pydub 是一个轻量音频处理库,核心价值在于用 Python 对象封装音频片段,切片、拼接、导出都围绕 AudioSegment 展开。它不直接完成 MP3、AAC、FLAC 等格式的编解码,而是调用 ffmpeg 和 ffprobe。因此安装 pydub 只是一行命令,真正决定脚本能否运行的是 ffmpeg 是否可用。
一、环境准备与 ffmpeg 检查
安装 pydub:- pip install pydub
- poetry add pydub
- uv add pydub
复制代码
验证版本更稳妥:- from importlib.metadata import version
- print(version('pydub'))
复制代码 原文提到 pydub.__version__ 在较新版本中可能不存在。
ffmpeg 安装方式:Windows 可用 winget install ffmpeg,并把 bin 目录加入 PATH;macOS 用 brew install ffmpeg;Ubuntu/Debian 用 sudo apt update && sudo apt install ffmpeg;CentOS/RHEL 用 sudo yum install epel-release && sudo yum install ffmpeg。安装后执行 ffmpeg -version 验证。
如果 ffmpeg 不在 PATH,可手动指定:- from pydub import AudioSegment
- from pydub.utils import which
- AudioSegment.converter = '/usr/local/bin/ffmpeg'
- AudioSegment.ffprobe = '/usr/local/bin/ffprobe'
- print(which('ffmpeg'))
复制代码 AudioSegment.ffprobe 也要指向正确路径,因为读取音频时长、采样率等元数据时会调用 ffprobe。
快速验证环境:- from pydub import AudioSegment
- silence = AudioSegment.silent(duration=1000)
- print(f'时长: {len(silence)}ms, 声道数: {silence.channels}, 帧率: {silence.frame_rate}Hz')
- silence.export('test.wav', format='wav')
复制代码 如果抛 FileNotFoundError 或 Couldn't find ffmpeg,优先检查 ffmpeg。Python 3.12+ 导入 pydub 报 ModuleNotFoundError 时,可能是 pip 指向了错误解释器,可用 python -m pip install pydub。
二、AudioSegment 创建与核心属性
从文件创建:- from pydub import AudioSegment
- song = AudioSegment.from_file('demo.mp3', format='mp3')
- wav_audio = AudioSegment.from_wav('demo.wav')
- raw_ogg = AudioSegment.from_ogg('demo.ogg')
复制代码 format 可省略,但显式指定能避免 .m4a 等扩展名歧义。中文路径建议先用 pathlib.Path 处理,再用 str() 传入。
从原始 PCM 数据创建:- import wave
- with wave.open('voice.wav', 'rb') as wf:
- raw_data = wf.readframes(wf.getnframes())
- seg = AudioSegment(
- data=raw_data,
- sample_width=wf.getsampwidth(),
- frame_rate=wf.getframerate(),
- channels=wf.getnchannels()
- )
复制代码 data 必须是 bytes,sample_width、frame_rate、channels 三个参数缺一不可,且必须与实际数据匹配,否则播放或导出会破音或静音。
核心属性:- print(seg.frame_rate)
- print(seg.channels)
- print(seg.sample_width)
- print(seg.duration_seconds)
- total_frames = seg.frame_count()
- byte_rate = seg.frame_rate * seg.channels * seg.sample_width
复制代码 duration_seconds 是浮点秒,切片时用毫秒整数更精确。不要用 seg.duration_seconds * 1000 做右边界,因为浮点误差可能多切或少切 1ms。安全做法是用 len(seg) 或 seg.frame_count()。AudioSegment 不可变,set_frame_rate、set_channels、set_sample_width 等方法都返回新对象。批量处理时注意复用短片段,避免反复从源文件加载。
三、切片、拼接与导出
切片单位是毫秒:- song = AudioSegment.from_mp3('input.mp3')
- clip = song[10000:20000]
- head = song[:5000]
- tail = song[30000:]
- last_3s = song[-3000:]
复制代码 start 超出总长度会返回空对象;end 大于总长度会自动截到末尾,不会补静音。
拼接用加号:- intro = AudioSegment.from_wav('intro.wav')
- outro = AudioSegment.from_wav('outro.wav')
- full = intro + outro
- combined = song[:5000] + song[10000:15000] + song[-3000:]
复制代码 拼接要求声道数、帧率、采样宽度一致。不一致时先统一:- mono = AudioSegment.from_wav('mono.wav')
- stereo = mono.set_channels(2)
- ok = stereo + outro
复制代码
导出与格式转换:- clip.export('output.wav', format='wav')
- clip.export('output.mp3', format='mp3', bitrate='192k')
- clip.export('output.ogg', format='ogg')
- from io import BytesIO
- buf = BytesIO()
- clip.export(buf, format='mp3')
- buf.seek(0)
复制代码 format 可省略,会根据扩展名推断,但显式指定更稳。format 与扩展名不一致时以 format 参数为准。空切片 export 会生成 0 字节文件,需要自行判断 len()。
批量切片示例:- source = AudioSegment.from_file('podcast.mp3')
- segments = {
- 'opening': source[:15000],
- 'middle': source[60000:90000],
- 'ending': source[-10000:],
- }
- for name, seg in segments.items():
- seg.export(f'{name}.wav', format='wav')
- seg.export(f'{name}.ogg', format='ogg')
复制代码
四、完整示例:截取 MP3 前 30 秒并转 WAV
脚本:- from pydub import AudioSegment
- from pathlib import Path
- import sys
- def cut_and_convert(input_path: str, output_path: str, duration_ms: int = 30_000):
- try:
- if not Path(input_path).exists():
- raise FileNotFoundError(f'输入文件不存在: {input_path}')
- audio = AudioSegment.from_mp3(input_path)
- print(f'原始音频时长: {len(audio)} ms, 声道数: {audio.channels}, 采样率: {audio.frame_rate} Hz')
- actual_duration = min(duration_ms, len(audio))
- segment = audio[:actual_duration]
- segment.export(output_path, format='wav')
- print(f'成功导出: {output_path} (实际时长 {len(segment)} ms)')
- return True
- except FileNotFoundError as e:
- print(f'文件错误: {e}', file=sys.stderr)
- except Exception as e:
- print(f'处理失败: {type(e).__name__}: {e}', file=sys.stderr)
- print('若提示 ffmpeg 相关错误,请先安装 FFmpeg 并确认可被系统找到。', file=sys.stderr)
- return False
- if __name__ == '__main__':
- success = cut_and_convert('podcast.mp3', 'intro_30s.wav')
- if not success:
- sys.exit(1)
复制代码
说明:from_mp3 内部调用 ffmpeg 解码;len(audio) 返回毫秒。min 是防御性写法,原音频不足 30 秒时不会误报。export 中 format='wav' 指定容器;WAV 无损,不需要 bitrate。异常捕获包括 ffmpeg 缺失、解码失败、权限错误等。若源为 3 分 20 秒,输出原始时长 200000 ms、双声道、44100 Hz,导出 30000 ms,文件约 5.3 MB。不要用 audio[:duration_seconds * 1000] 切片,浮点误差可能切出 30001ms。
五、进阶:音量、淡入淡出与循环拼接
音量调整:- song = AudioSegment.from_file('intro.mp3', format='mp3')
- louder = song.apply_gain(3.0)
- quieter = song.apply_gain(-10.0)
- louder.export('intro_louder.mp3', format='mp3')
- quieter.export('intro_quieter.mp3', format='mp3')
复制代码 apply_gain 参数是分贝,不是线性百分比。+6dB 约等于音量翻倍,-6dB 约减半。要线性降低 50%,可用 20 * log10(0.5) 约等于 -6.02,传 -6 近似。
淡入淡出:- clip = song[10000:18000].fade_in(1000).fade_out(2000)
- clip.export('clip_with_fade.mp3', format='mp3')
复制代码 fade_in 和 fade_out 单位毫秒,返回新对象,可链式调用。淡入时长不能超过音频总长度。
多段拼接与循环:- part1 = AudioSegment.from_file('part1.mp3')
- part2 = AudioSegment.from_file('part2.mp3')
- part3 = AudioSegment.from_file('part3.mp3')
- part1 = part1.set_channels(1).set_frame_rate(44100).set_sample_width(2)
- part2 = part2.set_channels(1).set_frame_rate(44100).set_sample_width(2)
- part3 = part3.set_channels(1).set_frame_rate(44100).set_sample_width(2)
- combined = part1 + part2 + part3
- combined.export('combined.mp3', format='mp3')
- loop_audio = combined * 3
复制代码 拼接前统一格式,避免帧率、声道、采样宽度不一致引发异常或隐式转换。audio * n 会生成完整新对象,循环次数极大时注意内存。
组合提示音:- source = AudioSegment.from_file('source.wav')
- melody_a = source[0:3000].fade_in(200).fade_out(300)
- melody_b = source[5000:8000].fade_in(150).fade_out(250)
- melody_a = melody_a.set_channels(1).set_frame_rate(44100)
- melody_b = melody_b.set_channels(1).set_frame_rate(44100)
- loop_sound = (melody_a + melody_b) * 2
- loop_sound.export('loop_notification.wav', format='wav')
复制代码 先切片再 fade 通常更高效;先 fade 再切片会截断淡出效果。
六、常见坑与适用场景
没有 ffmpeg 时,pydub 只能处理 WAV 和原始 PCM,MP3、AAC、FLAC 都会失败,常见报错为 FileNotFoundError、Couldn't find ffmpeg。AudioSegment.ffprobe 也要正确指向,否则读取元数据可能出错。sample_width=2 是 16-bit,sample_width=3 可能是 24-bit AIFF,直接转 MP3 会有转换开销,建议先 set_sample_width(2)。pydub 适合脚本化批量切片、播客片段提取、提示音拼接、格式转换、网页音频预处理等场景;超长音频或极大循环次数应评估内存,必要时改用 ffmpeg 命令行。 |