pydub音频切片与格式转换:ffmpeg依赖与毫秒切片实践
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
head = song[:5000]
tail = song
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 + 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,
'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.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.fade_in(200).fade_out(300)
melody_b = source.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 命令行。
Re: pydub音频切片与格式转换:ffmpeg依赖与毫秒切片实践
整理得很扎实,尤其是把 pydub 只是封装、真正干活的是 ffmpeg 和 ffprobe 这一点放在前面,能帮很多人少走弯路。实际排查时确实不能只盯着 pydub 安装,ffmpeg 和 ffprobe 的路径、PATH 都要确认,手动指定 AudioSegment.converter 和 AudioSegment.ffprobe 这招很实用。AudioSegment 不可变、set 开头的方法返回新对象,以及用毫秒整数和 len(seg) 或 frame_count() 来切片,避免 duration_seconds 浮点误差,这些细节很值得记下来。拼接前统一声道数、帧率、采样宽度,导出时显式指定 format,也能减少很多格式和扩展名带来的坑。感谢分享,内容对做音频批处理脚本很有帮助。Re: pydub音频切片与格式转换:ffmpeg依赖与毫秒切片实践
感谢分享,内容很实用。pydub 最容易踩的坑确实就是 ffmpeg 和 ffprobe 的依赖问题,之前我只把 ffmpeg 放进 PATH,忘了 ffprobe,结果读取音频时长和元数据时直接报错,后来把 AudioSegment.converter 和 AudioSegment.ffprobe 都指定成绝对路径才正常。你强调毫秒切片用 len(seg) 或 frame_count 做边界,而不是 duration_seconds 乘 1000,这点很有帮助,浮点误差在批量切音频时真的很容易多切或少切 1ms。 拼接前统一声道数、帧率、采样宽度也很关键,我遇到过不同来源的音频直接相加后声音变调或者导出静音的情况,后来都是先 set_channels、set_frame_rate、set_sample_width 处理一遍再拼。还有 AudioSegment 不可变这一点,批量处理时先加载一次源文件,再反复切片和拼接,比每次 from_file 快很多。期待你把导出和格式转换部分补完,尤其是 bitrate 和不同格式参数那一段。Re: pydub音频切片与格式转换:ffmpeg依赖与毫秒切片实践
楼主这篇总结挺到位的,尤其把 ffmpeg 这个“隐藏依赖”单独拎出来讲,很多人第一次用 pydub 就是卡在装完库却跑不起来,报错信息还容易误以为是 Python 环境问题。 几个我自己踩过的点,跟帖补充一下: 一是 Windows 上把 ffmpeg 的 bin 加进 PATH 之后,最好新开一个终端再验证,旧终端不会刷新环境变量,容易误判成没装成功。另外像 conda 环境里有时会自带一个老版本 ffmpeg,跟系统那个混着用,出问题时要先 which ffmpeg 确认到底调的是哪一个。 二是 AudioSegment.ffprobe 确实容易被忽略。只设 converter 不设 ffprobe,导出能跑,但读取元数据或者某些格式转换时就出幺蛾子,表现还不太一致。所以楼主的建议很实在,两个路径一起指。 三是切片那部分,用 duration_seconds 乘 1000 当右边界这个坑我中过,尤其是批量切几百上千个片段的时候,偶尔就有一两个文件多 1ms 或者少 1ms,混在结果里很难发现。后来统一改成用 len(seg) 和毫秒整数算边界,问题就没了。 四是你提到的同一批处理里声道数、帧率、采样宽度不一致导致拼接出错,我现在的习惯是写个 normalize 函数,进管道前先统一成 16bit、单声道或立体声、44.1kHz,后面就省心很多。反正 AudioSegment
页:
[1]