feat: add WhisperKit transcriber and transcribe probe

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 09:48:17 +02:00
co-authored by Claude Sonnet 5.5
parent 28515d9c87
commit ee8d56c2fb
5 changed files with 149 additions and 2 deletions
+17
View File
@@ -0,0 +1,17 @@
import Foundation
import SstWhisper
func runTranscribeProbe() async {
guard let samples = await recordSamples(seconds: 6) else { return }
print("Model laden (eerste keer: download ≈1.5 GB)…")
let transcriber = WhisperKitTranscriber()
do {
try await transcriber.load()
let started = Date.now
let text = try await transcriber.transcribe(samples)
let elapsed = Date.now.timeIntervalSince(started)
print(String(format: "(%.2f s) ", elapsed) + text)
} catch {
print("mislukt:", error.localizedDescription)
}
}
+3 -1
View File
@@ -6,6 +6,8 @@ case "hotkey":
runHotkeyProbe()
case "record":
await runRecordProbe()
case "transcribe":
await runTranscribeProbe()
default:
print("usage: sst-probe hotkey|record")
print("usage: sst-probe hotkey|record|transcribe")
}
@@ -0,0 +1,40 @@
import Foundation
import SstCore
import WhisperKit
public final class WhisperKitTranscriber: Transcriber {
public enum TranscriberError: Error, LocalizedError {
case notLoaded
public var errorDescription: String? { "Spraakmodel is nog niet geladen." }
}
private let modelName: String
private var pipeline: WhisperKit?
public init(model: String = "large-v3-v20240930_turbo") {
self.modelName = model
}
/// Downloads the model on first run (≈1.5 GB) and loads it into memory.
public func load() async throws {
pipeline = try await WhisperKit(WhisperKitConfig(model: modelName))
}
public func transcribe(_ samples: [Float]) async throws -> String {
guard let pipeline else { throw TranscriberError.notLoaded }
// language nil + detectLanguage: Whisper picks NL or EN per utterance.
let options = DecodingOptions(
task: .transcribe,
language: nil,
detectLanguage: true,
skipSpecialTokens: true,
withoutTimestamps: true
)
let results = try await pipeline.transcribe(audioArray: samples, decodeOptions: options)
return results
.map(\.text)
.joined(separator: " ")
.trimmingCharacters(in: .whitespacesAndNewlines)
}
}