Windows 桌面工具:对下载器保存的“文件名无意义、容器/编码不规范”的音频文件,用 FFmpeg/FFprobe 检测真实格式 → 优先无损重封装(stream copy)→ 写入音乐元数据 → 复验,产出结构规范、播放器兼容性好、可直接上传网易云音乐云盘的文件。
核心原则(按优先级):
- 绝不破坏原始音频(原文件只读,输出到
原目录/output/); - 尽可能保持原始音质(默认
-c:a copy无损重封装,绝不静默重新编码); - 只认 FFprobe 检测出的真实编码/容器,不信任文件扩展名;
- 输出结构规范的 MP3 / M4A / Opus / OGG / FLAC / WAV;
- 正确写入标题、歌手、专辑等元数据(MP3 用 ID3v2.3);
- 处理后用 FFprobe 复验,验证失败则任务失败并保留一切原文件。
audio-normalizer/
├── app/
│ ├── main.py # 程序入口(FFmpeg 检测 + 启动 GUI)
│ ├── core/ # 核心逻辑(与 GUI 完全解耦)
│ │ ├── ffmpeg.py # FFmpeg/FFprobe 定位 + 参数数组执行封装
│ │ ├── probe.py # FFprobe JSON 解析 → AudioInfo
│ │ ├── plan.py # 策略引擎:编码×容器映射表、扩展名不一致检测
│ │ ├── metadata.py # 8 字段元数据模型 → FFmpeg 参数
│ │ └── processor.py # 检测→计划→执行→降级→复验 编排
│ ├── ui/
│ │ └── main_window.py # PySide6 主窗口(表格/表单/线程/确认桥)
│ └── utils/
│ ├── filename.py # 命名模板 + Windows 非法字符过滤
│ └── logger.py # logs/ 按日日志 + 结构化任务记录
├── scripts/
│ ├── make_test_samples.py # 生成典型格式测试样例
│ └── smoke_test.py # 核心逻辑回归冒烟测试
├── tests/samples/ # 测试样例(脚本生成)
├── requirements.txt
└── README.md
核心处理逻辑(app/core/)不依赖任何 GUI,以后做命令行或 Web UI 可复用。
:: 1. 创建环境(Python 3.12,PySide6 要求 ≥3.9)
conda create -n audio-normalizer python=3.12 -y
:: 2. 激活并安装依赖
conda activate audio-normalizer
pip install -r requirements.txt若你的 conda 镜像源(如清华
pkgs/free)已失效报 404,可加--override-channels -c https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ -c https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/临时绕过,不影响全局配置。
程序启动时按以下顺序查找 ffmpeg / ffprobe(缺一不可):
- 环境变量
AUDIO_NORMALIZER_FFMPEG_DIR指定的目录; - 程序目录(源码运行 = 项目根目录;打包后 = exe 所在目录);
- 系统 PATH。
方式 A(推荐,winget):
winget install Gyan.FFmpeg装完重新打开终端即可(ffmpeg -version 验证)。
方式 B(绿色版):
- 从 https://www.gyan.dev/ffmpeg/builds/ 下载
ffmpeg-release-essentials.zip; - 解压后把
bin\ffmpeg.exe和bin\ffprobe.exe两个文件放到程序目录(项目根目录,或打包后的 exe 同目录)。
找不到时程序会弹中文提示,不会崩溃。
conda activate audio-normalizer
cd path\to\audio-normalizer
python -m app.main(若 conda run -n audio-normalizer python -m app.main 在你的 conda 版本上偶发插件报错,请改用 conda activate。)
使用流程:
- 点「选择文件」或「选择文件夹」(文件夹会递归收集音频,自动跳过
output/目录); - 列表自动逐项检测:真实格式、时长、处理策略;扩展名与真实格式不一致的会标
⚠(如文件名.mp3、实际 AAC);「信息」列显示该文件的标题/歌手; - 双击某行查看「处理前预览」:容器、编码、采样率、声道、码率、时长、推荐处理方式、是否重编码;
- 单击某行,右侧表单载入该文件的音乐信息(自动预填原文件已有的标签,可修改),编辑实时保存到该文件;信息均可留空;
- 想批量填写时:编辑好一组信息后点「应用到所有文件」(各文件仍可单独再改);多选时为只读,避免误操作;
- 底部选择输出目录(留空 = 每个原文件目录下的
output/)和文件名模板({title} - {artist}/{artist} - {title}/{track} - {title});属性全空时输出文件名使用原文件名; - 点「开始处理」:逐文件按各自的属性 检测 → 无损重封装+写标签(一次 FFmpeg 调用)→ FFprobe 复验 → 完成;单个文件失败不影响其他文件。
不会静默重编码: 只有以下情况会重编码,且每次都先弹窗确认(明确提示“有损转换”):
- 无损重封装失败时(询问是否降级重编码);
- 勾选「强制转换为 MP3」时;
- 遇到未知编码(如 WAVPACK/APE),需选择目标格式(MP3/M4A/OGG/WAV/FLAC)。
| 真实编码(FFprobe) | 目标容器 | 处理 |
|---|---|---|
| MP3 | .mp3 |
无损重封装,ID3v2.3 标签 |
| AAC / ALAC | .m4a |
无损重封装(ADTS AAC 或 .mp3 伪装的 AAC 也会正确转成 M4A) |
| Opus | .opus |
无损重封装 |
| Vorbis | .ogg |
无损重封装 |
| FLAC | .flac |
无损重封装 |
| PCM (WAV) | .wav |
无损重封装 |
| 其他(未知) | 用户选择 | 确认后重编码 |
命名模板自动过滤 Windows 非法字符 \ / : * ? " < > |;同名输出自动追加 (1)、 (2)…,绝不覆盖任何已有文件。
:: 1. 生成 6 种典型样例(含“文件名 .mp3、实为 ADTS AAC”的下载器典型情况)
conda activate audio-normalizer
python scripts/make_test_samples.py
:: 2. 核心逻辑回归测试(识别/重封装/标签/复验/降级/跳过,全自动断言)
python scripts/smoke_test.py
:: 3. GUI 冒烟测试(无窗口模式构建界面、添加文件、检测更新、预览)
python scripts/gui_smoke_test.py生成的样例(tests/samples/):
| 文件 | 扩展名 | 真实格式 | 预期处理 |
|---|---|---|---|
standard_mp3.mp3 |
.mp3 | MP3 | 无损重封装为标准 MP3 |
fake_aac.mp3 |
.mp3 | ADTS AAC | ⚠ 不一致 → 无损封装为 M4A |
sample.m4a |
.m4a | AAC/M4A | 无损重封装 |
sample.opus |
.opus | Opus/OGG | 无损重封装 |
sample.flac |
.flac | FLAC | 无损重封装 |
sample.wav |
.wav | PCM WAV | 无损重封装 |
手动验证输出文件(示例):
ffprobe -v quiet -print_format json -show_format -show_streams "tests\samples\output\测试歌曲 - 测试歌手.m4a"
:: 检查 format.duration > 0、streams[0].codec_name == "aac"、format.tags.title 等conda activate audio-normalizer
pip install pyinstaller
:: 项目根目录下执行
pyinstaller --noconfirm --onefile --windowed --name AudioNormalizer ^
--paths app ^
--collect-all PySide6 ^
app/main.py- 产物:
dist\AudioNormalizer.exe(单文件,双击运行,无控制台黑窗); - 把
ffmpeg.exe、ffprobe.exe放到 exe 同目录(程序自动优先从 exe 目录查找); - 日志在 exe 同目录的
logs/下; - 首次启动 PyInstaller 打包较慢属正常;若杀毒软件误报,加白名单即可。
- 封面图会被丢弃:处理命令
-map 0:a:0只保留音频流(保证与网易云云盘上传兼容性优先);封面保留可作为后续增强。 - 每个文件独立的元数据:单击行编辑该文件信息,编辑实时保存;「应用到所有文件」可批量填充;检测时会自动预填原文件已有的标签(手动编辑过的行不会被覆盖)。
- WAV 容器没有“专辑艺术家”字段(RIFF INFO 限制),该字段在 WAV 输出中不写入也不校验。
- 原始文件在成功处理前不会被删除或覆盖;验证失败时保留原文件与半成品输出(文件名已保证不冲突),并在日志中记录失败原因。
- 日志:
logs/audio_normalizer_YYYY-MM-DD.log(控制台同步输出),每个文件记录 原文件/原始扩展名/真实容器/真实编码/输出格式/是否重编码/FFmpeg 命令/结果/失败原因/输出路径。