import os
import json
import argparse
from pathlib import Path
#简单易用的音频处理库 pydub 支持加载、切片、格式转换、音量调整,底层依赖ffmpeg
#关键功能就是 audiosegment.from_wav("file.wav") 加载wav文件,audio[start_ms:end_ms]按毫秒切片 clip.export("out.wav",format="wav")导出音频
from pydub import AudioSegment
#这个函数,里面的三个参数是jsonl路径,音频路径,输出路径
def split_audio_by_jsonl(jsonl_path, audio_dir, output_dir):
audio_dir = Path(audio_dir)#为啥要这样?将传入的字符转换为一个pathlib.path对象,因为path可以拼接路径audio_dir / "file.wav",而不是 os.path.join(audio_dir, "file.wav")。
#还可以直接调用方法.exists() .is_file()等
output_dir = Path(output_dir)#指向命令行参数,意义同上
#parents=true 如果目录路径中包含多级不存在的父目录,自动递归创建所有上级目录
output_dir.mkdir(parents=True, exist_ok=True)#如果没有便创建
#打开jsonl文件,逐行读取
with open(jsonl_path, 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line:
continue
#json.loads(line)将字符串line 解析为Python字典item
#.get("key") 获取字段key
try:
item = json.loads(line)
key = item.get("key")
#.get("utterances",[])获得utterances字段,若缺失则默认为空列表
utterances = item.get("utterances", [])
if not key or not utterances:
print(f"⚠️ 跳过第 {line_num} 行:缺少 key 或 utterances")
continue
#看一下这段代码,首先是直接audio_dir/key作为路径,没有的话 加上wav后缀,要是还没有就是没有
查找原始音频文件(支持 .wav)
audio_path = audio_dir / key
if not audio_path.exists():
尝试带 .wav 后缀(如果 key 本身不含���
audio_path = audio_dir / (key + ".wav")
if not audio_path.exists():
print(f"❌ 第 {line_num} 行:未找到音频文件 {key}(或 {key}.wav)")
continue
print(f"🔊 正在处理: {key}")
#然后这个是找到了,用pydub库加载一个.wav格式的音频文件,将其转化为一个可编程操作的音频对象
pydub会调用底层音频引擎(通常是ffmpeg)读取文件;- 将音频数据加载到内存中,转换为
AudioSegment对象; - 对象内部包含:
- 音频采样率(如 16000 Hz)
- 通道数(单声道/立体声)
- 采样位深(如 16-bit)
- 原始音频数据(以
bytes或array存储)
audio = AudioSegment.from_wav(audio_path)
#对于utternaces 这里面很多条的 得到开始和结束的 得到text部分,我这个jsonl文件懒得粘贴条目,应该就是里面有 开始 结束 和text这三部分
for utt in utterances:
start_ms = utt.get("start_time")
end_ms = utt.get("end_time")
text = utt.get("text", "").strip()
if start_ms is None or end_ms is None:
continue
确保时间不越界
start_ms = max(0, start_ms)
end_ms = min(len(audio), end_ms)
#很健壮
if end_ms <= start_ms:
continue
切片
clip = audio[start_ms:end_ms]
生成文件名:key_start_end.wav
safe_key = key.replace(".wav", "") # 避免重复 .wav
#这是对于文件的命名,就是以文件名命名,但是不要.wav 然后加上开始和结束时间 作为命名
filename = f"{safe_key}{start_ms}{end_ms}.wav"
#构建路径
output_path = output_dir / filename
clip.export(output_path, format="wav")
print(f" ✅ 保存: {filename}")
except Exception as e:
print(f"❌ 第 {line_num} 行处理出错: {e}")
print(f"\n✅ 所有音频切分完成!输出目录: {output_dir}")
def main():
#命令行参数
parser = argparse.ArgumentParser(
description="根据 JSONL 中的 utterances 时间戳切分音频"
)
parser.add_argument("jsonl_file", help="输入的 .jsonl 文件路径")
parser.add_argument("audio_dir", help="原始音频所在文件夹")
parser.add_argument("output_dir", help="切分后音频的输出文件夹")
args = parser.parse_args()
split_audio_by_jsonl(args.jsonl_file, args.audio_dir, args.output_dir)
简单代码分析