Files
tinystt/Sources/TinySTT/Speech/TranscriptAccumulator.swift

40 lines
1.5 KiB
Swift

import Foundation
/// Speech results may revise their time boundaries when they become final.
/// Match coverage rather than an exact rounded start timestamp.
struct TranscriptAccumulator {
private struct Segment {
var start: Double
var end: Double
var text: String
}
private var finalized: [Segment] = []
private var volatile: [Segment] = []
private let tolerance = 0.001
var text: String {
(finalized + volatile).sorted { $0.start < $1.start }.map(\.text).joined()
}
mutating func apply(start: Double, end: Double, text: String, final: Bool) {
guard start.isFinite, end.isFinite, end >= start else { return }
let segment = Segment(start: start, end: end, text: text)
func overlaps(_ other: Segment) -> Bool {
abs(other.start - start) <= tolerance ||
(other.start < end - tolerance && other.end > start + tolerance)
}
if final {
finalized.removeAll(where: overlaps)
// A final result supersedes provisional coverage up to its end,
// including a slightly shifted start boundary.
volatile.removeAll { $0.end <= end + tolerance || overlaps($0) }
if !text.isEmpty { finalized.append(segment) }
} else {
volatile.removeAll(where: overlaps)
// Late provisional callbacks must never duplicate finalized audio.
guard !finalized.contains(where: overlaps) else { return }
if !text.isEmpty { volatile.append(segment) }
}
}
}