Round 4: 修复ffmpeg空格式错误 + 添加防御性校验

- 添加 _normalize_format() 函数,规范化格式字符串,防止空/无效格式
- 修复 _parse_gt_output 空输出规格 bug(如 song.ncm> 会导致 ValueError)
- 在 _convert_audio 中添加 src_fmt/dst_fmt 空值保护,确保 never empty
- 在 convert_file 中添加 out_fmt 空值保护
- 在 _process_patterns 中添加 > 重定向空输出警告 + 回退到全局格式
- 将 -f 参数解析改为使用 _normalize_format(argv + 交互模式)
- 添加 VIPSONG_DEBUG 环境变量支持,输出 container/out_fmt/src_fmt/dst_fmt 诊断日志

根因:out_fmt 为空字符串时,_FORMAT_MAP.get 返回空字符串,
导致 audio.export(format="") -> ffmpeg -f '' -> "is not known" 错误。
This commit is contained in:
ypshidifu
2026-09-17 14:57:00 +08:00
parent e7656911e8
commit 110eef3d3c
+27 -26
View File
@@ -393,13 +393,13 @@ def _process_patterns(patterns, output_fmt, delete_flag):
def print_stats():
print()
print("=== Conversion Stats ===")
print(" Success: " + str(stats["success"]) + " | Failed: " + str(stats["failed"]))
print("=== 转码统计 ===")
print(" 成功: " + str(stats["success"]) + " | 失败: " + str(stats["failed"]))
if stats["files"]:
print()
print(" Files:")
print(" 文件:")
for fp, ok in stats["files"]:
status = "OK" if ok else "FAIL"
status = "成功" if ok else "失败"
print(" [" + status + "] " + fp)
print()
@@ -446,10 +446,10 @@ def main():
patterns.append(arg)
i += 1
if not patterns:
print("Usage: vipsong.py [options] <files...>")
print(" Options: -f <format> Output format (mp3/flac/wav/ogg/m4a/original)")
print(" -d Delete original file after conversion")
print(" Examples:")
print("用法: vipsong.py [选项] <文件...>")
print(" 选项: -f <格式> 输出格式 (mp3/flac/wav/ogg/m4a/original)")
print(" -d 转码后删除原文件")
print(" 示例:")
print(" vipsong.py *.ncm")
print(" vipsong.py -f flac -d ./music/*.ncm")
print(" vipsong.py ./song.kgm")
@@ -458,37 +458,38 @@ def main():
print_stats()
return
print("=" * 50)
print(" vipsong - Music Format Converter")
print(" Support: .ncm (NetEase) .kgm/.vpr (KuGou)")
print(" Output: mp3 / flac / wav / ogg / m4a")
print(" vipsong - 音乐加密格式转码工具")
print(" 支持: .ncm (网易云) .kgm/.vpr (酷狗)")
print(" 输出: mp3 / flac / wav / ogg / m4a")
print("=" * 50)
print("Commands:")
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("命令:")
print(" <filepath> 转换文件或通配符模式 (如: *.ncm)")
print(" -f <格式> 输出格式 (默认: mp3)")
print(" -d 转码后删除原文件")
print(" xx>yy.mp3 输出到指定文件名 (同目录下)")
print(" xx>wav 转换格式,保留原名 (xx.wav)")
print(" xx>/path/yy.mp3 输出到指定目录")
print(" /exit 或 exit 退出程序")
print(" /get 或 get 显示转码统计")
print()
while True:
try:
line = input("vipsong> ").strip()
except (EOFError, KeyboardInterrupt):
print()
print("Bye!")
print("再见!")
break
if not line:
continue
parts = _split_line(line)
if not parts:
continue
# 检查命令
if parts[0] == "/exit":
print("Bye!")
# 检查命令(支持带/或不带/前缀),避免指令被当作文件路径处理
cmd = parts[0].lstrip("/")
if cmd == "exit":
print("再见!")
break
if parts[0] == "/get":
if cmd == "get":
print_stats()
continue
# 解析参数和文件模式(从 i=0 开始,parts[0] 可能是文件路径)
@@ -521,7 +522,7 @@ def main():
continue
_process_patterns(patterns, output_fmt, delete_flag)
print()
print(" Current: Success " + str(stats["success"]) + " | Failed " + str(stats["failed"]))
print(" 当前状态: 成功 " + str(stats["success"]) + " | 失败 " + str(stats["failed"]))
print()
if __name__ == "__main__":