Whisper API分片音频转录:修复SRT序号与时间戳问题
解决Whisper API分片转录SRT的拼接问题
针对长音频分片转录后SRT序号重置、时间戳错误、空行冗余的问题,直接给你可落地的Node.js/Express实现方案:
核心处理步骤
- 提前记录每个音频分片的时长:用工具获取每个分片的精确时长,后续用来修正时间戳
- 解析单分片的SRT内容:把每个分片的SRT拆成独立的字幕条目,提取序号、时间区间、文本
- 修正序号与时间戳:
- 序号:累加前面所有分片的字幕总条数,作为当前分片每条字幕的起始序号
- 时间戳:把当前分片每条字幕的时间都加上前面所有分片的总时长
- 重新生成标准SRT格式:统一用单空行分隔字幕条目,避免双空行
具体代码实现
1. 工具函数:获取音频分片时长
用fluent-ffmpeg库获取音频时长(先安装:npm install fluent-ffmpeg)
const ffmpeg = require('fluent-ffmpeg'); async function getAudioDuration(filePath) { return new Promise((resolve, reject) => { ffmpeg.ffprobe(filePath, (err, metadata) => { if (err) reject(err); // 转换为毫秒 resolve(Math.floor(metadata.format.duration * 1000)); }); }); }
2. 工具函数:解析SRT为字幕条目数组
function parseSrt(srtContent) { const entries = []; // 按多换行分割字幕条目,处理Whisper返回的格式 const rawEntries = srtContent.trim().split(/\n\n+/); rawEntries.forEach(entry => { const lines = entry.trim().split('\n'); if (lines.length < 3) return; const index = parseInt(lines[0].trim(), 10); const [startStr, endStr] = lines[1].trim().split(' --> '); entries.push({ index, start: srtTimeToMs(startStr), end: srtTimeToMs(endStr), text: lines.slice(2).join('\n').trim() }); }); return entries; } // SRT时间格式转毫秒 function srtTimeToMs(timeStr) { const [hours, minutes, seconds] = timeStr.split(':'); const [sec, ms] = seconds.split(','); return parseInt(hours) * 3600000 + parseInt(minutes) * 60000 + parseInt(sec) * 1000 + parseInt(ms); } // 毫秒转SRT时间格式 function msToSrtTime(ms) { const hours = Math.floor(ms / 3600000); const minutes = Math.floor((ms % 3600000) / 60000); const seconds = Math.floor((ms % 60000) / 1000); const remainingMs = ms % 1000; return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')},${remainingMs.toString().padStart(3, '0')}`; }
3. 拼接分片SRT的主函数
async function mergeChunkedSrts(chunkDataList) { let totalIndex = 0; let totalDuration = 0; const mergedEntries = []; for (const chunkData of chunkDataList) { const { srtContent, audioPath } = chunkData; // 获取当前分片时长 const chunkDuration = await getAudioDuration(audioPath); // 解析当前分片的SRT const chunkEntries = parseSrt(srtContent); // 修正每个条目的序号和时间戳 chunkEntries.forEach(entry => { mergedEntries.push({ index: totalIndex + entry.index, start: entry.start + totalDuration, end: entry.end + totalDuration, text: entry.text }); }); // 更新累计序号和时长 totalIndex += chunkEntries.length; totalDuration += chunkDuration; } // 生成最终的SRT内容 return mergedEntries.map(entry => { return `${entry.index}\n${msToSrtTime(entry.start)} --> ${msToSrtTime(entry.end)}\n${entry.text}`; }).join('\n\n'); }
4. 使用示例
// 假设你已经有了分片的SRT内容和对应的音频文件路径 const chunkDataList = [ { srtContent: '你的第一个分片SRT内容', audioPath: './audio/chunk1.mp3' }, { srtContent: '你的第二个分片SRT内容', audioPath: './audio/chunk2.mp3' } ]; // 合并SRT mergeChunkedSrts(chunkDataList).then(finalSrt => { console.log(finalSrt); // 这里可以把finalSrt写入文件或者返回给前端 }).catch(err => { console.error('合并失败:', err); });
额外说明
- 双空行问题:通过
parseSrt函数里的split(/\n\n+/)合并多空行,最后用join('\n\n')统一生成单空行分隔的标准SRT格式 - Whisper API返回的SRT可能有格式差异,比如有些换行不标准,
parseSrt里的逻辑可以根据实际返回的格式微调 - 音频分片要保证连续无重叠,否则时间戳会出错
内容的提问来源于stack exchange,提问作者mheavers
相关产品推荐
相关产品推荐

