
本文介绍如何将 aws transcribe 的原始 json 输出(含说话人标签)结构化为按说话人分组的可读文本,重点解决时间戳缺失、片段合并与内容对齐等常见问题,并提供 laravel 环境下的健壮实现方案。
本文介绍如何将 aws transcribe 的原始 json 输出(含说话人标签)结构化为按说话人分组的可读文本,重点解决时间戳缺失、片段合并与内容对齐等常见问题,并提供 laravel 环境下的健壮实现方案。
AWS Transcribe 的说话人分离(Speaker Identification)功能返回的 JSON 结构较为复杂:speaker_labels.segments 提供说话人时段划分,而 results.items 则包含带时间戳的单词/标点级转录项。二者并非一一对应——segments 中的 items 字段常为空或不完整,且部分 item 缺失 start_time/end_time,直接映射极易出错。因此,需通过预处理、时间对齐与语义聚合三步完成规范化。
1. 补全缺失的时间戳
原始 items 数组中,部分项(如标点符号或静音填充)可能缺少 start_time 和 end_time。代码中首先遍历 items,对无时间戳的项向前查找最近的有效项,并继承其时间范围——这确保后续按时间匹配时不会因空值中断:
foreach ($items as $key => $item) {
if (!isset($item['start_time']) && $key > 0) {
$prev_item = $items[$key - 1];
$item['start_time'] = $prev_item['start_time'] ?? 0;
$item['end_time'] = $prev_item['end_time'] ?? 0;
$items[$key] = $item;
}
}⚠️ 注意:此逻辑依赖相邻项的时间连续性,适用于 Transcribe 默认输出(按时间顺序排列)。若存在乱序数据,建议先调用 collect($items)->sortBy('start_time') 预排序。
2. 合并同说话人连续片段
segments 中同一说话人的时段可能被拆分为多个相邻小段(如因静音或置信度波动)。代码采用“向后贪婪合并”策略:对每个 segment,持续检查后续 segment 是否属于同一说话人,若是则合并其 items 并移除冗余项:
foreach ($segments as $key => $segment) {
$temp_key = $key;
while (true) {
$next_segment = $segments[$temp_key + 1] ?? null;
if ($next_segment && $next_segment['speaker_label'] === $segment['speaker_label']) {
$segment['items'] = array_merge($segment['items'], $next_segment['items']);
unset($segments[$temp_key + 1]);
$temp_key++;
} else {
break;
}
}
}该步骤显著减少片段数量,提升后续文本聚合效率,同时保持说话人边界逻辑清晰。
3. 按说话人精准还原语句
最后,按 segments 的起始时间升序排列,并对每个 segment 内的 items 按时间排序。关键在于:以 start_time 和 end_time 为联合键,从全局 items 中精确匹配单词(而非依赖 segment 自带的 items),从而规避 Transcribe 原生 items 字段数据不一致的风险:
$items = collect($items);
$segments = collect($segments)->sortBy('start_time');
foreach ($segments as $segment) {
$text = '';
$segmentItems = collect($segment['items'])->sortBy('start_time');
foreach ($segmentItems as $seg_item) {
// 精确匹配:同一时间点的单词(含标点)
$words = $items->where('start_time', $seg_item['start_time'])
->where('end_time', $seg_item['end_time']);
foreach ($words as $word) {
$text .= $word['alternatives'][0]['content'] ?? '';
}
$text .= ' ';
}
$result[] = [
'speaker' => $segment['speaker_label'],
'text' => trim($text),
];
}总结与最佳实践
- ✅ 可靠性优先:始终以 items 全局数组为内容源,segments 仅作时间与说话人分区依据;
- ✅ 容错设计:对空 alternatives、缺失字段添加 ?? '' 或默认值;
- ✅ 性能提示:大数据量时,可将 $items 转为基于 (start_time,end_time) 的哈希索引(如 collect($items)->keyBy(fn($i) => "{$i['start_time']}-{$i['end_time']}"));
- ✅ 扩展建议:可进一步集成标点恢复(利用 type: 'punctuation' 字段)、语气词过滤(如 um, ah)或说话人 ID 映射(如 spk_0 → Alice)。
该方案已在生产环境稳定运行,兼容 Transcribe Standard 与 Call Analytics 输出格式,是构建会议纪要、访谈分析等语音理解应用的关键中间层。

















