Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/vmanot/CorePersistence.git", branch: "main"),
.package(url: "https://github.com/vmanot/Media", branch: "main"),
.package(url: "https://github.com/vmanot/Merge.git", branch: "master"),
.package(url: "https://github.com/vmanot/NetworkKit.git", branch: "master"),
.package(url: "https://github.com/vmanot/Swallow.git", branch: "master"),
Expand All @@ -115,7 +116,7 @@ let package = Package(
"Merge",
"NetworkKit",
"Swallow",
"SwiftUIX",
"SwiftUIX"
],
path: "Sources/LargeLanguageModels",
resources: [
Expand Down Expand Up @@ -191,7 +192,8 @@ let package = Package(
"LargeLanguageModels",
"Merge",
"NetworkKit",
"Swallow"
"Swallow",
"Media"
],
path: "Sources/_Gemini",
swiftSettings: [
Expand Down Expand Up @@ -389,6 +391,7 @@ let package = Package(
"Ollama",
"OpenAI",
"Swallow",
"NeetsAI",
],
path: "Sources/AI",
swiftSettings: [
Expand Down
42 changes: 42 additions & 0 deletions Sources/AI/AnySpeechSynthesisRequestHandling.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// AnySpeechSynthesisRequestHandling.swift
// AI
//
// Created by Jared Davidson on 1/14/25.
//

import ElevenLabs
import LargeLanguageModels
import NeetsAI

public struct AnySpeechSynthesisRequestHandling: Hashable {
private let _hashValue: Int

public let base: any CoreMI._ServiceClientProtocol & SpeechSynthesisRequestHandling

public var displayName: String {
switch base {
case is ElevenLabs.Client:
return "ElevenLabs"
case is NeetsAI.Client:
return "NeetsAI"
default:
fatalError()
}
}

public init(
_ base: any CoreMI._ServiceClientProtocol & SpeechSynthesisRequestHandling
) {
self.base = base
self._hashValue = ObjectIdentifier(base as AnyObject).hashValue
}

public static func == (lhs: AnySpeechSynthesisRequestHandling, rhs: AnySpeechSynthesisRequestHandling) -> Bool {
lhs._hashValue == rhs._hashValue
}

public func hash(into hasher: inout Hasher) {
hasher.combine(_hashValue)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,6 @@ extension ElevenLabs.APISpecification {
context: DecodeOutputContext
) throws -> Output {
do {
if Input.self == RequestBodies.EditVoiceInput.self {
print("TEsts")
}
try response.validate()
} catch {
let apiError: Error
Expand Down
78 changes: 77 additions & 1 deletion Sources/ElevenLabs/Intramodular/ElevenLabs.Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ import SwiftAPI
import Merge
import FoundationX
import Swallow
import LargeLanguageModels

extension ElevenLabs {
@RuntimeDiscoverable
public final class Client: SwiftAPI.Client, ObservableObject {
public static var persistentTypeRepresentation: some IdentityRepresentation {
CoreMI._ServiceVendorIdentifier._ElevenLabs
}

public typealias API = ElevenLabs.APISpecification
public typealias Session = HTTPSession

Expand All @@ -33,6 +38,25 @@ extension ElevenLabs {
}
}

extension ElevenLabs.Client: CoreMI._ServiceClientProtocol {
public convenience init(
account: (any CoreMI._ServiceAccountProtocol)?
) async throws {
let account: any CoreMI._ServiceAccountProtocol = try account.unwrap()
let serviceVendorIdentifier: CoreMI._ServiceVendorIdentifier = try account.serviceVendorIdentifier.unwrap()

guard serviceVendorIdentifier == CoreMI._ServiceVendorIdentifier._ElevenLabs else {
throw CoreMI._ServiceClientError.incompatibleVendor(serviceVendorIdentifier)
}

guard let credential = try account.credential as? CoreMI._ServiceCredentialTypes.APIKeyCredential else {
throw CoreMI._ServiceClientError.invalidCredential(try account.credential)
}

self.init(apiKey: credential.apiKey)
}
}

extension ElevenLabs.Client {
public func availableVoices() async throws -> [ElevenLabs.Voice] {
try await run(\.listVoices).voices
Expand All @@ -50,7 +74,6 @@ extension ElevenLabs.Client {
voiceSettings: voiceSettings,
model: model
)

return try await run(\.textToSpeech, with: .init(voiceId: voiceID, requestBody: requestBody))
}

Expand Down Expand Up @@ -107,3 +130,56 @@ extension ElevenLabs.Client {
try await run(\.deleteVoice, with: voice.rawValue)
}
}

// MARK: - Conformances

extension ElevenLabs.Client: SpeechSynthesisRequestHandling {
public func availableVoices() async throws -> [AbstractVoice] {
return try await self.availableVoices().map({try $0.__conversion()})
}

public func speech(for text: String, voiceID: String, voiceSettings: AbstractVoiceSettings, model: String) async throws -> Data {
try await self.speech(
for: text,
voiceID: voiceID,
voiceSettings: .init(settings: voiceSettings),
model: .init(rawValue: model) ?? .MultilingualV1
)
}

public func speechToSpeech(inputAudioURL: URL, voiceID: String, voiceSettings: AbstractVoiceSettings, model: String) async throws -> Data {
try await self.speechToSpeech(
inputAudioURL: inputAudioURL,
voiceID: voiceID,
voiceSettings: .init(settings: voiceSettings),
model: .init(rawValue: model) ?? .MultilingualV1
)
}

public func upload(voiceWithName name: String, description: String, fileURL: URL) async throws -> AbstractVoice.ID {
let voice: ElevenLabs.Voice.ID = try await self.upload(
voiceWithName: name,
description: description,
fileURL: fileURL
)

return .init(rawValue: voice.rawValue)
}

public func edit(voice: AbstractVoice.ID, name: String, description: String, fileURL: URL?) async throws -> Bool {
try await self.edit(
voice: ElevenLabs.Voice.ID(rawValue: voice.rawValue),
name: name,
description: description,
fileURL: fileURL
)
}

public func delete(voice: AbstractVoice.ID) async throws {
try await self.delete(
voice: ElevenLabs.Voice.ID(
rawValue: voice.rawValue
)
)
}
}
22 changes: 22 additions & 0 deletions Sources/ElevenLabs/Intramodular/ElevenLabs.Voice.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import Foundation
import Swift
import LargeLanguageModels

extension ElevenLabs {
public struct Voice: Hashable, Identifiable, Sendable {
Expand Down Expand Up @@ -42,3 +43,24 @@ extension ElevenLabs.Voice: Codable {
case isOwner
}
}

extension ElevenLabs.Voice: AbstractVoiceConvertible {
public func __conversion() throws -> AbstractVoice {
return AbstractVoice(
voiceID: self.voiceID,
name: self.name,
description: self.description
)
}
}

extension ElevenLabs.Voice: AbstractVoiceInitiable {
public init(voice: AbstractVoice) throws {
self.init(
voiceID: voice.voiceID,
name: voice.name,
description: voice.description,
isOwner: nil
)
}
}
27 changes: 27 additions & 0 deletions Sources/ElevenLabs/Intramodular/ElevenLabs.VoiceSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//

import Foundation
import LargeLanguageModels

extension ElevenLabs {
public struct VoiceSettings: Codable, Sendable, Hashable {
Expand Down Expand Up @@ -98,3 +99,29 @@ extension ElevenLabs.VoiceSettings {
)
}
}

// MARK: - Conformances

extension ElevenLabs.VoiceSettings: AbstractVoiceSettingsConvertible {
public func __conversion() throws -> AbstractVoiceSettings {
return .init(
stability: stability,
similarityBoost: similarityBoost,
styleExaggeration: styleExaggeration,
speakerBoost: speakerBoost,
removeBackgroundNoise: removeBackgroundNoise
)
}
}

extension ElevenLabs.VoiceSettings: AbstractVoiceSettingsInitiable {
public init(settings: AbstractVoiceSettings) throws {
self.init(
stability: settings.stability,
similarityBoost: settings.similarityBoost,
styleExaggeration: settings.styleExaggeration,
speakerBoost: settings.speakerBoost,
removeBackgroundNoise: settings.removeBackgroundNoise
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// HumeAI+ElevenLabsClientProtocol.swift
// Voice
//
// Created by Jared Davidson on 11/22/24.
//

import Foundation
import SwiftUI
import AVFoundation
import LargeLanguageModels

extension HumeAI.Client: SpeechSynthesisRequestHandling {
public func availableVoices() async throws -> [AbstractVoice] {
return try await getAllAvailableVoices().map(
{ voice in
return AbstractVoice(
voiceID: voice.id,
name: voice.name,
description: nil
)
})
}

public func speech(for text: String, voiceID: String, voiceSettings: AbstractVoiceSettings, model: String) async throws -> Data {
throw HumeAI.APIError.unknown(message: "Text to speech not supported")
}

public func speechToSpeech(inputAudioURL: URL, voiceID: String, voiceSettings: AbstractVoiceSettings, model: String) async throws -> Data {
throw HumeAI.APIError.unknown(message: "Speech to speech not supported")
}

public func upload(voiceWithName name: String, description: String, fileURL: URL) async throws -> AbstractVoice.ID {
throw HumeAI.APIError.unknown(message: "Voice creation is not supported")
}

public func edit(voice: AbstractVoice.ID, name: String, description: String, fileURL: URL?) async throws -> Bool {
throw HumeAI.APIError.unknown(message: "Voice creation is not supported")
}

public func delete(voice: AbstractVoice.ID) async throws {
throw HumeAI.APIError.unknown(message: "Voice creation is not supported")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// AudioStore.swift
// Voice
//
// Created by Jared Davidson on 10/31/24.
//

import CorePersistence
import SwiftUI
import AVFoundation
import UniformTypeIdentifiers

public struct AbstractVoice: Codable, Hashable, Identifiable, Sendable {
public typealias ID = _TypeAssociatedID<Self, String>

public let id: ID
public let voiceID: String
public let name: String
public let description: String?

public init(
voiceID: String,
name: String,
description: String?
) {
self.id = .init(rawValue: voiceID)
self.voiceID = voiceID
self.name = name
self.description = description
}
}

// MARK: - Conformances

public protocol AbstractVoiceInitiable {
init(voice: AbstractVoice) throws
}

public protocol AbstractVoiceConvertible {
func __conversion() throws -> AbstractVoice
}
Loading