|
@@ -125,6 +125,10 @@ final class MeetingTranscriptionService {
|
|
|
let counter = ProgressCounter(total: totalChunks, onProgress: onProgress)
|
|
let counter = ProgressCounter(total: totalChunks, onProgress: onProgress)
|
|
|
await counter.emitInitial()
|
|
await counter.emitInitial()
|
|
|
|
|
|
|
|
|
|
+ // If the caller passes explicit locales, treat them as the user's preferred transcription languages.
|
|
|
|
|
+ // In that case we should try locales in the given order and only fall back when a locale
|
|
|
|
|
+ // produces an empty transcript for the chunk.
|
|
|
|
|
+ let userProvidedLocales = locales.isEmpty == false
|
|
|
let effectiveLocales = locales.isEmpty ? preferredLocalesForGlobalRecognition() : locales
|
|
let effectiveLocales = locales.isEmpty ? preferredLocalesForGlobalRecognition() : locales
|
|
|
|
|
|
|
|
async let micSegments: [TranscriptSegment] = {
|
|
async let micSegments: [TranscriptSegment] = {
|
|
@@ -134,6 +138,7 @@ final class MeetingTranscriptionService {
|
|
|
chunks: plan.1,
|
|
chunks: plan.1,
|
|
|
speaker: .microphone,
|
|
speaker: .microphone,
|
|
|
locales: effectiveLocales,
|
|
locales: effectiveLocales,
|
|
|
|
|
+ preferLocaleOrder: userProvidedLocales,
|
|
|
counter: counter
|
|
counter: counter
|
|
|
)
|
|
)
|
|
|
}()
|
|
}()
|
|
@@ -144,6 +149,7 @@ final class MeetingTranscriptionService {
|
|
|
chunks: plan.1,
|
|
chunks: plan.1,
|
|
|
speaker: .system,
|
|
speaker: .system,
|
|
|
locales: effectiveLocales,
|
|
locales: effectiveLocales,
|
|
|
|
|
+ preferLocaleOrder: userProvidedLocales,
|
|
|
counter: counter
|
|
counter: counter
|
|
|
)
|
|
)
|
|
|
}()
|
|
}()
|
|
@@ -196,6 +202,7 @@ final class MeetingTranscriptionService {
|
|
|
chunks: [ChunkPlan],
|
|
chunks: [ChunkPlan],
|
|
|
speaker: TranscriptSpeaker,
|
|
speaker: TranscriptSpeaker,
|
|
|
locales: [Locale],
|
|
locales: [Locale],
|
|
|
|
|
+ preferLocaleOrder: Bool,
|
|
|
counter: ProgressCounter
|
|
counter: ProgressCounter
|
|
|
) async throws -> [TranscriptSegment] {
|
|
) async throws -> [TranscriptSegment] {
|
|
|
var segments: [TranscriptSegment] = []
|
|
var segments: [TranscriptSegment] = []
|
|
@@ -208,7 +215,8 @@ final class MeetingTranscriptionService {
|
|
|
let result = await transcribeBufferWithLocaleFallback(
|
|
let result = await transcribeBufferWithLocaleFallback(
|
|
|
buffer: buffer,
|
|
buffer: buffer,
|
|
|
locales: locales,
|
|
locales: locales,
|
|
|
- preferredLocale: preferredLocale
|
|
|
|
|
|
|
+ preferredLocale: preferredLocale,
|
|
|
|
|
+ preferLocaleOrder: preferLocaleOrder
|
|
|
)
|
|
)
|
|
|
await counter.increment()
|
|
await counter.increment()
|
|
|
preferredLocale = result.locale ?? preferredLocale
|
|
preferredLocale = result.locale ?? preferredLocale
|
|
@@ -245,7 +253,8 @@ final class MeetingTranscriptionService {
|
|
|
private func transcribeBufferWithLocaleFallback(
|
|
private func transcribeBufferWithLocaleFallback(
|
|
|
buffer: AVAudioPCMBuffer,
|
|
buffer: AVAudioPCMBuffer,
|
|
|
locales: [Locale],
|
|
locales: [Locale],
|
|
|
- preferredLocale: Locale?
|
|
|
|
|
|
|
+ preferredLocale: Locale?,
|
|
|
|
|
+ preferLocaleOrder: Bool
|
|
|
) async -> (text: String, locale: Locale?) {
|
|
) async -> (text: String, locale: Locale?) {
|
|
|
var orderedLocales: [Locale] = []
|
|
var orderedLocales: [Locale] = []
|
|
|
if let preferredLocale {
|
|
if let preferredLocale {
|
|
@@ -255,6 +264,30 @@ final class MeetingTranscriptionService {
|
|
|
orderedLocales.append(locale)
|
|
orderedLocales.append(locale)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // When the caller provides explicit locales (Preferred Language 1/2), prioritize that order.
|
|
|
|
|
+ // We only use the heuristic "bestCandidate" scoring for auto locale selection.
|
|
|
|
|
+ if preferLocaleOrder {
|
|
|
|
|
+ for locale in orderedLocales {
|
|
|
|
|
+ guard let recognizer = SFSpeechRecognizer(locale: locale), recognizer.isAvailable else { continue }
|
|
|
|
|
+ do {
|
|
|
|
|
+ let text = try await transcribeBuffer(buffer: buffer, recognizer: recognizer)
|
|
|
|
|
+ let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
|
+ guard trimmed.isEmpty == false else { continue }
|
|
|
|
|
+ return (trimmed, locale)
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ // One transient retry before moving on to the next locale.
|
|
|
|
|
+ try? await Task.sleep(nanoseconds: 500_000_000)
|
|
|
|
|
+ if let text = try? await transcribeBuffer(buffer: buffer, recognizer: recognizer) {
|
|
|
|
|
+ let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
|
+ guard trimmed.isEmpty == false else { continue }
|
|
|
|
|
+ return (trimmed, locale)
|
|
|
|
|
+ }
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return ("", nil)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
var bestCandidate: (text: String, locale: Locale, score: Double)?
|
|
var bestCandidate: (text: String, locale: Locale, score: Double)?
|
|
|
for locale in orderedLocales {
|
|
for locale in orderedLocales {
|
|
|
guard let recognizer = SFSpeechRecognizer(locale: locale), recognizer.isAvailable else { continue }
|
|
guard let recognizer = SFSpeechRecognizer(locale: locale), recognizer.isAvailable else { continue }
|