diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..20256d6 --- /dev/null +++ b/Package.resolved @@ -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 +} diff --git a/Package.swift b/Package.swift index afdba95..ddf5d78 100644 --- a/Package.swift +++ b/Package.swift @@ -4,9 +4,19 @@ import PackageDescription let package = Package( name: "Sst", platforms: [.macOS(.v14)], + dependencies: [ + .package(url: "https://github.com/argmaxinc/WhisperKit.git", from: "0.9.0"), + ], targets: [ .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"]), ], swiftLanguageModes: [.v5] diff --git a/Sources/SstProbe/TranscribeProbe.swift b/Sources/SstProbe/TranscribeProbe.swift new file mode 100644 index 0000000..aed61b6 --- /dev/null +++ b/Sources/SstProbe/TranscribeProbe.swift @@ -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) + } +} diff --git a/Sources/SstProbe/main.swift b/Sources/SstProbe/main.swift index 70723b1..32f3641 100644 --- a/Sources/SstProbe/main.swift +++ b/Sources/SstProbe/main.swift @@ -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") } diff --git a/Sources/SstWhisper/WhisperKitTranscriber.swift b/Sources/SstWhisper/WhisperKitTranscriber.swift new file mode 100644 index 0000000..fe8a520 --- /dev/null +++ b/Sources/SstWhisper/WhisperKitTranscriber.swift @@ -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) + } +}