feat: add WhisperKit transcriber and transcribe probe
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"originHash" : "e6c9264e0494528c9e107c3e12b51524d9f3fe957d63a07601ae76077888c441",
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "swift-argument-parser",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-argument-parser.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "6a52f3251125d74daf04fcbd5e6f08a75d074382",
|
||||||
|
"version" : "1.8.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-asn1",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-asn1.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "3b6410f7dee09eb33cdd26260c5fd47fda19b0e2",
|
||||||
|
"version" : "1.7.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-collections",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-collections.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "98ef3c98609a1e31b7e157b5b619579001a789d6",
|
||||||
|
"version" : "1.7.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-crypto",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-crypto.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "da9d28d69ebe3894b18376c8f2395c2f37b8448f",
|
||||||
|
"version" : "4.5.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-jinja",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/huggingface/swift-jinja.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "4588064a20f3fc093c95f2f7d3359999bf30cae5",
|
||||||
|
"version" : "2.5.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-transformers",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/huggingface/swift-transformers.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "150169bfba0889c229a2ce7494cf8949f18e6906",
|
||||||
|
"version" : "1.1.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "whisperkit",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/argmaxinc/WhisperKit.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "e2adabbe7d98dc4d0ab9a5b75424ecc42a9cdbef",
|
||||||
|
"version" : "0.18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "yyjson",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/ibireme/yyjson.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "8b4a38dc994a110abaec8a400615567bd996105f",
|
||||||
|
"version" : "0.12.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 3
|
||||||
|
}
|
||||||
+11
-1
@@ -4,9 +4,19 @@ import PackageDescription
|
|||||||
let package = Package(
|
let package = Package(
|
||||||
name: "Sst",
|
name: "Sst",
|
||||||
platforms: [.macOS(.v14)],
|
platforms: [.macOS(.v14)],
|
||||||
|
dependencies: [
|
||||||
|
.package(url: "https://github.com/argmaxinc/WhisperKit.git", from: "0.9.0"),
|
||||||
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(name: "SstCore"),
|
.target(name: "SstCore"),
|
||||||
.executableTarget(name: "SstProbe", dependencies: ["SstCore"]),
|
.target(
|
||||||
|
name: "SstWhisper",
|
||||||
|
dependencies: [
|
||||||
|
"SstCore",
|
||||||
|
.product(name: "WhisperKit", package: "WhisperKit"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.executableTarget(name: "SstProbe", dependencies: ["SstCore", "SstWhisper"]),
|
||||||
.testTarget(name: "SstCoreTests", dependencies: ["SstCore"]),
|
.testTarget(name: "SstCoreTests", dependencies: ["SstCore"]),
|
||||||
],
|
],
|
||||||
swiftLanguageModes: [.v5]
|
swiftLanguageModes: [.v5]
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ case "hotkey":
|
|||||||
runHotkeyProbe()
|
runHotkeyProbe()
|
||||||
case "record":
|
case "record":
|
||||||
await runRecordProbe()
|
await runRecordProbe()
|
||||||
|
case "transcribe":
|
||||||
|
await runTranscribeProbe()
|
||||||
default:
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user