使用AudioSegment合并大MP3文件时触发MemoryError(内存充足)
解决Pydub合并大MP3文件触发MemoryError的问题
我明白你遇到的困扰——小文件合并没问题,大文件就内存溢出,这其实是Pydub的工作机制导致的:Pydub会把整个音频文件解码成未压缩的PCM格式加载到内存中,而MP3压缩后的体积和解码后的PCM体积差距极大(比如128kbps的MP3,解码后体积大概是原文件的10倍以上),大文件很容易就把内存撑爆了。
下面给你几个实用的解决方案,按推荐程度排序:
方案一:改用FFmpeg流式合并(最推荐)
Pydub底层依赖FFmpeg,不如直接调用FFmpeg的原生concat功能,它是流式处理,完全不需要把整个文件加载到内存里,效率和内存占用都友好很多。
修改后的代码示例
首先确保你已经安装了FFmpeg,并将其添加到系统PATH中(或者在代码里指定FFmpeg的完整路径):
from pydub import AudioSegment import eyed3 import os import psutil import subprocess def make_files(path_to_files, audiofiles): pre_sermon_path = "pre_sermon.mp3" # 确保输出目录存在 os.makedirs('combined', exist_ok=True) for file_name in audiofiles: sermon_path = os.path.join(path_to_files, file_name) output_path = os.path.join('combined', file_name) # 1. 创建临时的合并列表文件(FFmpeg concat需要) temp_concat_file = "temp_concat_list.txt" with open(temp_concat_file, 'w', encoding='utf-8') as f: # 写入要合并的两个文件路径(必须是绝对路径,避免FFmpeg报错) f.write(f"file '{os.path.abspath(pre_sermon_path)}'\n") f.write(f"file '{os.path.abspath(sermon_path)}'\n") # 2. 获取原音频的标签信息 audio_metadata = {} try: audiofile = eyed3.load(sermon_path) if audiofile.tag: audio_metadata['title'] = audiofile.tag.title or "" audio_metadata['artist'] = audiofile.tag.artist or "" audio_metadata['album'] = audiofile.tag.album or "" audio_metadata['comment'] = audiofile.tag.comments[0].text if audiofile.tag.comments else "" except Exception as e: print(f"读取{file_name}标签失败:{e}") # 3. 构建FFmpeg命令:合并音频+设置标签+指定比特率 ffmpeg_cmd = [ 'ffmpeg', '-f', 'concat', # 使用concat demuxer模式 '-safe', '0', # 允许使用绝对路径 '-i', temp_concat_file, # 输入合并列表 '-b:a', '128k', # 设置输出比特率 '-y', # 覆盖已存在的输出文件 ] # 添加元数据参数 for key, value in audio_metadata.items(): ffmpeg_cmd.extend(['-metadata', f'{key}={value}']) # 添加输出路径 ffmpeg_cmd.append(output_path) # 4. 执行FFmpeg命令 try: subprocess.run(ffmpeg_cmd, check=True, capture_output=True, text=True) print(f"✅ 成功合并:{file_name}") except subprocess.CalledProcessError as e: print(f"❌ 合并{file_name}失败:{e.stderr}") finally: # 删除临时文件 if os.path.exists(temp_concat_file): os.remove(temp_concat_file) # 原有逻辑保持不变 general_path = 'C:\\projects\\python\\files\\mp3\\sermons\\' files = set(os.listdir('sermons/')) combined_files = set(os.listdir('combined/')) difference = {filename: f"{os.stat(os.path.join(general_path, filename)).st_size/1000000:.2f} MB" for filename in (files - combined_files)} print("当前内存状态:", psutil.virtual_memory()) print("待处理文件:", difference) make_files('sermons/', difference.keys())
为什么这个方案更好?
- FFmpeg是专业的音视频处理工具,流式处理不会加载整个文件到内存,大文件也能轻松处理
- 处理速度比Pydub更快,因为不需要解码再重新编码(如果原文件编码参数一致,甚至可以直接拷贝流,不过这里因为要统一比特率还是会重新编码)
方案二:优化Pydub的内存使用(不推荐,仅作参考)
如果一定要用Pydub,可以尝试分块读取大文件,但实现起来比较麻烦,而且内存优化效果有限。核心思路是把大文件切成小块,合并后再逐块拼接:
def load_large_mp3(path, chunk_size=60*1000): # 每次加载60秒的块 audio = AudioSegment.empty() start = 0 while True: try: chunk = AudioSegment.from_mp3(path)[start:start+chunk_size] if len(chunk) == 0: break audio += chunk start += chunk_size gc.collect() # 每加载一块就回收内存 except Exception as e: print(f"加载块失败:{e}") break return audio
然后在make_files里用这个函数替代AudioSegment.from_mp3,但这个方法依然会占用不少内存,只是比直接加载整个文件好一点,还是不如FFmpeg方案靠谱。
方案三:升级Python版本(辅助优化)
你使用的Python3.6.2已经比较老旧了,新版本的Python(比如3.8+)在内存管理和垃圾回收上有不少优化,升级后可能会缓解内存问题,但这只能作为辅助方案,不能从根本上解决大文件加载的问题。
内容的提问来源于stack exchange,提问作者Aliaksei Aksionau
相关产品推荐
相关产品推荐

