04db423416
- 70 skills with code and documentation - Add .gitignore (ignore __pycache__, output/, temp/, venv/) - Clean up test intermediates and caches
193 lines
5.5 KiB
Python
193 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Audio Merger - 音频合并工具
|
||
|
||
功能:
|
||
- 合并多个音频文件为一个完整版
|
||
- 自动按文件名排序
|
||
- 支持指定输出文件名
|
||
- 保持原有文件不变
|
||
|
||
依赖:
|
||
- ffmpeg(必须安装并添加到PATH)
|
||
|
||
使用方法:
|
||
python scripts/merge_audio.py <audio_dir> [options]
|
||
|
||
示例:
|
||
# 合并指定目录所有MP3
|
||
python scripts/merge_audio.py ./audio_chapters
|
||
|
||
# 指定输出文件名
|
||
python scripts/merge_audio.py ./audio_chapters --output 完整版.mp3
|
||
|
||
# 指定文件模式(默认*.mp3)
|
||
python scripts/merge_audio.py ./audio_chapters --pattern "*.mp3"
|
||
"""
|
||
|
||
import os
|
||
import argparse
|
||
import subprocess
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
|
||
def get_audio_files(audio_dir: str, pattern: str = "*.mp3") -> list:
|
||
"""获取目录中的音频文件并按文件名排序"""
|
||
audio_path = Path(audio_dir)
|
||
if not audio_path.exists():
|
||
print(f"错误: 目录不存在 {audio_dir}")
|
||
return []
|
||
|
||
files = list(audio_path.glob(pattern))
|
||
files.sort()
|
||
|
||
# 过滤掉已经存在的完整版文件(避免重复合并)
|
||
files = [
|
||
f for f in files if "完整版" not in f.name and "complete" not in f.name.lower()
|
||
]
|
||
|
||
return [str(f) for f in files]
|
||
|
||
|
||
def merge_audio_files(audio_files: list, output_file: str, audio_dir: str) -> bool:
|
||
"""使用 ffmpeg 合并音频文件"""
|
||
|
||
if not audio_files:
|
||
print("错误: 没有找到音频文件")
|
||
return False
|
||
|
||
print(f"找到 {len(audio_files)} 个音频文件")
|
||
print("文件列表:")
|
||
for i, f in enumerate(audio_files[:5], 1):
|
||
print(f" {i}. {os.path.basename(f)}")
|
||
if len(audio_files) > 5:
|
||
print(f" ... 还有 {len(audio_files) - 5} 个文件")
|
||
print()
|
||
|
||
# 创建 ffmpeg concat 列表文件
|
||
concat_file = os.path.join(audio_dir, ".merge_list.txt")
|
||
try:
|
||
with open(concat_file, "w", encoding="utf-8") as f:
|
||
for audio_file in audio_files:
|
||
# 使用相对路径
|
||
rel_path = os.path.relpath(audio_file, audio_dir)
|
||
f.write(f"file '{rel_path}'\n")
|
||
|
||
print(f"开始合并到: {os.path.basename(output_file)}")
|
||
|
||
# 构建 ffmpeg 命令
|
||
cmd = [
|
||
"ffmpeg",
|
||
"-y", # 覆盖已存在的文件
|
||
"-f",
|
||
"concat",
|
||
"-safe",
|
||
"0",
|
||
"-i",
|
||
concat_file,
|
||
"-c",
|
||
"copy", # 直接复制,不重新编码
|
||
output_file,
|
||
]
|
||
|
||
# 执行合并
|
||
result = subprocess.run(cmd, capture_output=True, cwd=audio_dir)
|
||
|
||
if result.returncode == 0:
|
||
file_size = os.path.getsize(output_file) / (1024 * 1024)
|
||
print(f"✓ 合并成功!")
|
||
print(f" 输出文件: {os.path.basename(output_file)}")
|
||
print(f" 文件大小: {file_size:.1f} MB")
|
||
print(f" 包含文件: {len(audio_files)} 个")
|
||
return True
|
||
else:
|
||
print(f"✗ 合并失败")
|
||
error_msg = (
|
||
result.stderr.decode("utf-8", errors="ignore")
|
||
if result.stderr
|
||
else "未知错误"
|
||
)
|
||
if "ffmpeg" in error_msg.lower() and "not found" in error_msg.lower():
|
||
print(" 提示: 未找到 ffmpeg,请先安装 ffmpeg 并添加到 PATH")
|
||
else:
|
||
print(f" 错误: {error_msg[:200]}")
|
||
return False
|
||
|
||
finally:
|
||
# 清理临时文件
|
||
if os.path.exists(concat_file):
|
||
os.remove(concat_file)
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(
|
||
description="音频文件合并工具",
|
||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||
epilog="""
|
||
示例:
|
||
%(prog)s ./audio_chapters
|
||
%(prog)s ./audio_chapters --output 完整版.mp3
|
||
%(prog)s ./audio_chapters --pattern "chapter_*.mp3"
|
||
|
||
注意:
|
||
需要先安装 ffmpeg: https://ffmpeg.org/download.html
|
||
""",
|
||
)
|
||
|
||
parser.add_argument("audio_dir", help="音频文件所在目录")
|
||
parser.add_argument(
|
||
"--output", "-o", help="输出文件名(默认: 00_完整版_合并音频.mp3)"
|
||
)
|
||
parser.add_argument(
|
||
"--pattern", default="*.mp3", help="文件匹配模式(默认: *.mp3)"
|
||
)
|
||
|
||
args = parser.parse_args()
|
||
|
||
# 检查目录
|
||
if not os.path.isdir(args.audio_dir):
|
||
print(f"错误: 目录不存在 {args.audio_dir}")
|
||
sys.exit(1)
|
||
|
||
# 获取音频文件
|
||
audio_files = get_audio_files(args.audio_dir, args.pattern)
|
||
|
||
if not audio_files:
|
||
print("没有找到音频文件,退出")
|
||
sys.exit(1)
|
||
|
||
# 确定输出文件名
|
||
if args.output:
|
||
output_file = os.path.join(args.audio_dir, args.output)
|
||
else:
|
||
output_file = os.path.join(args.audio_dir, "00_完整版_合并音频.mp3")
|
||
|
||
# 检查输出文件是否已存在
|
||
if os.path.exists(output_file):
|
||
print(f"警告: 输出文件已存在,将被覆盖: {os.path.basename(output_file)}")
|
||
response = input("是否继续? (y/n): ")
|
||
if response.lower() != "y":
|
||
print("已取消")
|
||
sys.exit(0)
|
||
|
||
print("=" * 60)
|
||
print("音频合并工具")
|
||
print("=" * 60)
|
||
print()
|
||
|
||
# 执行合并
|
||
success = merge_audio_files(audio_files, output_file, args.audio_dir)
|
||
|
||
print()
|
||
print("=" * 60)
|
||
if success:
|
||
print("合并完成!")
|
||
else:
|
||
print("合并失败!")
|
||
print("=" * 60)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|