查看: 274|回复: 0

pydub音频切片与格式转换:ffmpeg依赖与毫秒切片实践

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
pydub 是一个轻量音频处理库,核心价值在于用 Python 对象封装音频片段,切片、拼接、导出都围绕 AudioSegment 展开。它不直接完成 MP3、AAC、FLAC 等格式的编解码,而是调用 ffmpeg 和 ffprobe。因此安装 pydub 只是一行命令,真正决定脚本能否运行的是 ffmpeg 是否可用。

一、环境准备与 ffmpeg 检查

安装 pydub:
  1. pip install pydub
  2. poetry add pydub
  3. uv add pydub
复制代码

验证版本更稳妥:
  1. from importlib.metadata import version
  2. 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,可手动指定:
  1. from pydub import AudioSegment
  2. from pydub.utils import which
  3. AudioSegment.converter = '/usr/local/bin/ffmpeg'
  4. AudioSegment.ffprobe = '/usr/local/bin/ffprobe'
  5. print(which('ffmpeg'))
复制代码
AudioSegment.ffprobe 也要指向正确路径,因为读取音频时长、采样率等元数据时会调用 ffprobe。

快速验证环境:
  1. from pydub import AudioSegment
  2. silence = AudioSegment.silent(duration=1000)
  3. print(f'时长: {len(silence)}ms, 声道数: {silence.channels}, 帧率: {silence.frame_rate}Hz')
  4. silence.export('test.wav', format='wav')
复制代码
如果抛 FileNotFoundError 或 Couldn't find ffmpeg,优先检查 ffmpeg。Python 3.12+ 导入 pydub 报 ModuleNotFoundError 时,可能是 pip 指向了错误解释器,可用 python -m pip install pydub。

二、AudioSegment 创建与核心属性

从文件创建:
  1. from pydub import AudioSegment
  2. song = AudioSegment.from_file('demo.mp3', format='mp3')
  3. wav_audio = AudioSegment.from_wav('demo.wav')
  4. raw_ogg = AudioSegment.from_ogg('demo.ogg')
复制代码
format 可省略,但显式指定能避免 .m4a 等扩展名歧义。中文路径建议先用 pathlib.Path 处理,再用 str() 传入。

从原始 PCM 数据创建:
  1. import wave
  2. with wave.open('voice.wav', 'rb') as wf:
  3.     raw_data = wf.readframes(wf.getnframes())
  4.     seg = AudioSegment(
  5.         data=raw_data,
  6.         sample_width=wf.getsampwidth(),
  7.         frame_rate=wf.getframerate(),
  8.         channels=wf.getnchannels()
  9.     )
复制代码
data 必须是 bytes,sample_width、frame_rate、channels 三个参数缺一不可,且必须与实际数据匹配,否则播放或导出会破音或静音。

核心属性:
  1. print(seg.frame_rate)
  2. print(seg.channels)
  3. print(seg.sample_width)
  4. print(seg.duration_seconds)
  5. total_frames = seg.frame_count()
  6. 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 等方法都返回新对象。批量处理时注意复用短片段,避免反复从源文件加载。

三、切片、拼接与导出

切片单位是毫秒:
  1. song = AudioSegment.from_mp3('input.mp3')
  2. clip = song[10000:20000]
  3. head = song[:5000]
  4. tail = song[30000:]
  5. last_3s = song[-3000:]
复制代码
start 超出总长度会返回空对象;end 大于总长度会自动截到末尾,不会补静音。

拼接用加号:
  1. intro = AudioSegment.from_wav('intro.wav')
  2. outro = AudioSegment.from_wav('outro.wav')
  3. full = intro + outro
  4. combined = song[:5000] + song[10000:15000] + song[-3000:]
复制代码
拼接要求声道数、帧率、采样宽度一致。不一致时先统一:
  1. mono = AudioSegment.from_wav('mono.wav')
  2. stereo = mono.set_channels(2)
  3. ok = stereo + outro
复制代码

导出与格式转换:
  1. clip.export('output.wav', format='wav')
  2. clip.export('output.mp3', format='mp3', bitrate='192k')
  3. clip.export('output.ogg', format='ogg')
  4. from io import BytesIO
  5. buf = BytesIO()
  6. clip.export(buf, format='mp3')
  7. buf.seek(0)
复制代码
format 可省略,会根据扩展名推断,但显式指定更稳。format 与扩展名不一致时以 format 参数为准。空切片 export 会生成 0 字节文件,需要自行判断 len()。

批量切片示例:
  1. source = AudioSegment.from_file('podcast.mp3')
  2. segments = {
  3.     'opening': source[:15000],
  4.     'middle': source[60000:90000],
  5.     'ending': source[-10000:],
  6. }
  7. for name, seg in segments.items():
  8.     seg.export(f'{name}.wav', format='wav')
  9.     seg.export(f'{name}.ogg', format='ogg')
复制代码

四、完整示例:截取 MP3 前 30 秒并转 WAV

脚本:
  1. from pydub import AudioSegment
  2. from pathlib import Path
  3. import sys
  4. def cut_and_convert(input_path: str, output_path: str, duration_ms: int = 30_000):
  5.     try:
  6.         if not Path(input_path).exists():
  7.             raise FileNotFoundError(f'输入文件不存在: {input_path}')
  8.         audio = AudioSegment.from_mp3(input_path)
  9.         print(f'原始音频时长: {len(audio)} ms, 声道数: {audio.channels}, 采样率: {audio.frame_rate} Hz')
  10.         actual_duration = min(duration_ms, len(audio))
  11.         segment = audio[:actual_duration]
  12.         segment.export(output_path, format='wav')
  13.         print(f'成功导出: {output_path} (实际时长 {len(segment)} ms)')
  14.         return True
  15.     except FileNotFoundError as e:
  16.         print(f'文件错误: {e}', file=sys.stderr)
  17.     except Exception as e:
  18.         print(f'处理失败: {type(e).__name__}: {e}', file=sys.stderr)
  19.         print('若提示 ffmpeg 相关错误,请先安装 FFmpeg 并确认可被系统找到。', file=sys.stderr)
  20.     return False
  21. if __name__ == '__main__':
  22.     success = cut_and_convert('podcast.mp3', 'intro_30s.wav')
  23.     if not success:
  24.         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。

五、进阶:音量、淡入淡出与循环拼接

音量调整:
  1. song = AudioSegment.from_file('intro.mp3', format='mp3')
  2. louder = song.apply_gain(3.0)
  3. quieter = song.apply_gain(-10.0)
  4. louder.export('intro_louder.mp3', format='mp3')
  5. quieter.export('intro_quieter.mp3', format='mp3')
复制代码
apply_gain 参数是分贝,不是线性百分比。+6dB 约等于音量翻倍,-6dB 约减半。要线性降低 50%,可用 20 * log10(0.5) 约等于 -6.02,传 -6 近似。

淡入淡出:
  1. clip = song[10000:18000].fade_in(1000).fade_out(2000)
  2. clip.export('clip_with_fade.mp3', format='mp3')
复制代码
fade_in 和 fade_out 单位毫秒,返回新对象,可链式调用。淡入时长不能超过音频总长度。

多段拼接与循环:
  1. part1 = AudioSegment.from_file('part1.mp3')
  2. part2 = AudioSegment.from_file('part2.mp3')
  3. part3 = AudioSegment.from_file('part3.mp3')
  4. part1 = part1.set_channels(1).set_frame_rate(44100).set_sample_width(2)
  5. part2 = part2.set_channels(1).set_frame_rate(44100).set_sample_width(2)
  6. part3 = part3.set_channels(1).set_frame_rate(44100).set_sample_width(2)
  7. combined = part1 + part2 + part3
  8. combined.export('combined.mp3', format='mp3')
  9. loop_audio = combined * 3
复制代码
拼接前统一格式,避免帧率、声道、采样宽度不一致引发异常或隐式转换。audio * n 会生成完整新对象,循环次数极大时注意内存。

组合提示音:
  1. source = AudioSegment.from_file('source.wav')
  2. melody_a = source[0:3000].fade_in(200).fade_out(300)
  3. melody_b = source[5000:8000].fade_in(150).fade_out(250)
  4. melody_a = melody_a.set_channels(1).set_frame_rate(44100)
  5. melody_b = melody_b.set_channels(1).set_frame_rate(44100)
  6. loop_sound = (melody_a + melody_b) * 2
  7. 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 命令行。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-15 14:59 , Processed in 0.022482 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部