修复完整路径输入与>重定向语法,修正int.from_bytes字节序参数类型

This commit is contained in:
ypshidifu
2026-09-17 13:48:40 +08:00
parent 3af7501b47
commit e7656911e8
+88 -17
View File
@@ -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()