修复完整路径输入与>重定向语法,修正int.from_bytes字节序参数类型
This commit is contained in:
+88
-17
@@ -25,11 +25,21 @@ _FORMAT_MAP = {
|
||||
"mp4": "mp4",
|
||||
}
|
||||
|
||||
_EXT_TO_FMT = {
|
||||
"mp3": "mp3",
|
||||
"flac": "flac",
|
||||
"wav": "wav",
|
||||
"ogg": "ogg",
|
||||
"oga": "ogg",
|
||||
"m4a": "mp4",
|
||||
"mp4": "mp4",
|
||||
}
|
||||
|
||||
stats = {"success": 0, "failed": 0, "files": []}
|
||||
kgm_decoder = None
|
||||
|
||||
def _u32le(data, pos):
|
||||
return struct.unpack(b"<I", data[pos:pos+4])[0]
|
||||
return struct.unpack("<I", data[pos:pos+4])[0]
|
||||
|
||||
def _sniff_container(header):
|
||||
"""从魔术字节检测音频容器类型。"""
|
||||
@@ -59,6 +69,23 @@ def _extension_for(fmt):
|
||||
}
|
||||
return mapping.get(fmt, "mp3")
|
||||
|
||||
def _parse_gt_output(input_path, output_spec):
|
||||
"""解析 'input>output' 重定向语法,返回 (output_path, output_fmt)。"""
|
||||
p = Path(input_path)
|
||||
spec = output_spec
|
||||
# 如果包含路径分隔符,作为完整路径
|
||||
if "/" in spec or "\\" in spec:
|
||||
out_path = Path(spec)
|
||||
elif "." in spec:
|
||||
# 仅文件名,放在输入文件同目录
|
||||
out_path = p.parent / spec
|
||||
else:
|
||||
# 裸格式名,仅修改扩展名
|
||||
out_path = p.with_suffix("." + spec)
|
||||
ext = out_path.suffix.lower().lstrip(".")
|
||||
out_fmt = _EXT_TO_FMT.get(ext, ext)
|
||||
return str(out_path), out_fmt
|
||||
|
||||
def ncm_stream_bytes(key):
|
||||
sbox = bytearray(range(256))
|
||||
j = 0
|
||||
@@ -74,7 +101,7 @@ def ncm_stream_bytes(key):
|
||||
def ncm_decrypt_audio(data, box):
|
||||
reps = (len(data) + 256) // 256 + 1
|
||||
stream = (box * reps)[1:1 + len(data)]
|
||||
return (int.from_bytes(data, b"little") ^ int.from_bytes(stream, b"little")).to_bytes(len(data), b"little")
|
||||
return (int.from_bytes(data, "little") ^ int.from_bytes(stream, "little")).to_bytes(len(data), "little")
|
||||
|
||||
def decrypt_ncm(file_path):
|
||||
data = Path(file_path).read_bytes()
|
||||
@@ -262,7 +289,7 @@ def _convert_audio(raw, container, out_fmt, output_path):
|
||||
audio = AudioSegment.from_file(io.BytesIO(raw), format=src_fmt)
|
||||
audio.export(output_path, format=dst_fmt)
|
||||
|
||||
def convert_file(file_path, output_fmt, delete_flag):
|
||||
def convert_file(file_path, output_fmt="mp3", delete_flag=False, output_path=None):
|
||||
global stats, kgm_decoder
|
||||
file_path = str(Path(file_path).resolve())
|
||||
if not Path(file_path).exists():
|
||||
@@ -307,7 +334,8 @@ def convert_file(file_path, output_fmt, delete_flag):
|
||||
stats["files"].append((file_path, False))
|
||||
return
|
||||
out_fmt = container if output_fmt == "original" else output_fmt
|
||||
output_path = _resolve_output(file_path, out_fmt)
|
||||
if output_path is None:
|
||||
output_path = _resolve_output(file_path, out_fmt)
|
||||
try:
|
||||
_convert_audio(raw, container, out_fmt, output_path)
|
||||
except Exception as e:
|
||||
@@ -344,6 +372,25 @@ def expand_files(patterns):
|
||||
result.append(pat)
|
||||
return result
|
||||
|
||||
def _process_patterns(patterns, output_fmt, delete_flag):
|
||||
"""处理文件模式列表,支持 > 重定向语法。"""
|
||||
for pat in patterns:
|
||||
if ">" in pat:
|
||||
input_spec, output_spec = pat.split(">", 1)
|
||||
input_spec = input_spec.strip()
|
||||
output_spec = output_spec.strip()
|
||||
if not input_spec:
|
||||
print(" [WARN] > 重定向语法缺少输入文件: " + pat)
|
||||
continue
|
||||
files = expand_files([input_spec])
|
||||
for f in files:
|
||||
out_path, out_fmt = _parse_gt_output(f, output_spec)
|
||||
convert_file(f, out_fmt, delete_flag, output_path=out_path)
|
||||
else:
|
||||
files = expand_files([pat])
|
||||
for f in files:
|
||||
convert_file(f, output_fmt, delete_flag)
|
||||
|
||||
def print_stats():
|
||||
print()
|
||||
print("=== Conversion Stats ===")
|
||||
@@ -356,6 +403,27 @@ def print_stats():
|
||||
print(" [" + status + "] " + fp)
|
||||
print()
|
||||
|
||||
def _split_line(line):
|
||||
"""按空白分割命令行,处理引号但不处理反斜杠转义(适配 Windows 路径)。"""
|
||||
args = []
|
||||
current = []
|
||||
in_single = False
|
||||
in_double = False
|
||||
for ch in line:
|
||||
if ch == '"' and not in_single:
|
||||
in_double = not in_double
|
||||
elif ch == "'" and not in_double:
|
||||
in_single = not in_single
|
||||
elif ch in (' ', '\t') and not in_single and not in_double:
|
||||
if current:
|
||||
args.append(''.join(current))
|
||||
current = []
|
||||
else:
|
||||
current.append(ch)
|
||||
if current:
|
||||
args.append(''.join(current))
|
||||
return args
|
||||
|
||||
def main():
|
||||
global stats
|
||||
if len(sys.argv) > 1:
|
||||
@@ -386,9 +454,7 @@ def main():
|
||||
print(" vipsong.py -f flac -d ./music/*.ncm")
|
||||
print(" vipsong.py ./song.kgm")
|
||||
sys.exit(1)
|
||||
files = expand_files(patterns)
|
||||
for f in files:
|
||||
convert_file(f, output_fmt, delete_flag)
|
||||
_process_patterns(patterns, output_fmt, delete_flag)
|
||||
print_stats()
|
||||
return
|
||||
print("=" * 50)
|
||||
@@ -400,6 +466,9 @@ def main():
|
||||
print(" <filepath> Convert file or glob pattern (e.g. *.ncm)")
|
||||
print(" -f <format> Output format (default: mp3)")
|
||||
print(" -d Delete original after conversion")
|
||||
print(" xx>yy.mp3 Output to specified filename (same dir)")
|
||||
print(" xx>wav Convert to format, keep name (xx.wav)")
|
||||
print(" xx>/path/yy.mp3 Output to absolute path")
|
||||
print(" /exit Exit")
|
||||
print(" /get Show stats")
|
||||
print()
|
||||
@@ -412,20 +481,21 @@ def main():
|
||||
break
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split()
|
||||
parts = _split_line(line)
|
||||
if not parts:
|
||||
continue
|
||||
cmd = parts[0]
|
||||
if cmd == "/exit":
|
||||
# 检查命令
|
||||
if parts[0] == "/exit":
|
||||
print("Bye!")
|
||||
break
|
||||
elif cmd == "/get":
|
||||
if parts[0] == "/get":
|
||||
print_stats()
|
||||
continue
|
||||
# 解析参数和文件模式(从 i=0 开始,parts[0] 可能是文件路径)
|
||||
patterns = []
|
||||
output_fmt = "mp3"
|
||||
delete_flag = False
|
||||
i = 1
|
||||
i = 0
|
||||
while i < len(parts):
|
||||
if parts[i] == "-f" and i + 1 < len(parts):
|
||||
output_fmt = parts[i + 1].lower()
|
||||
@@ -433,22 +503,23 @@ def main():
|
||||
elif parts[i] == "-d":
|
||||
delete_flag = True
|
||||
i += 1
|
||||
elif parts[i].startswith("-"):
|
||||
elif parts[i].startswith("-") and len(parts[i]) > 1 and not Path(parts[i]).exists():
|
||||
print(" [WARN] Unknown option: " + parts[i])
|
||||
i += 1
|
||||
else:
|
||||
patterns.append(parts[i])
|
||||
i += 1
|
||||
if not patterns:
|
||||
print(" Usage: <filepath> [-f format] [-d]")
|
||||
print(" Usage: <filepath> [-f format] [-d] 或 <filepath>>output.xxx")
|
||||
print(" Examples: *.ncm")
|
||||
print(" ./xx/xx.kgm")
|
||||
print(" song.ncm -d")
|
||||
print(" *.ncm -f flac")
|
||||
print(" xx.ncm>yy.mp3 (重命名输出)")
|
||||
print(" xx.kgm>wav (转换格式)")
|
||||
print(" xx.ncm>/xx/xx.mp3 (指定输出目录)")
|
||||
continue
|
||||
files = expand_files(patterns)
|
||||
for f in files:
|
||||
convert_file(f, output_fmt, delete_flag)
|
||||
_process_patterns(patterns, output_fmt, delete_flag)
|
||||
print()
|
||||
print(" Current: Success " + str(stats["success"]) + " | Failed " + str(stats["failed"]))
|
||||
print()
|
||||
|
||||
Reference in New Issue
Block a user