Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ElevenLabs-DotNet 4.0.0 #67

Draft
wants to merge 6 commits into
base: dev/4.0.0
Choose a base branch
from
Draft
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
120 changes: 78 additions & 42 deletions ElevenLabs-DotNet/Authentication/ElevenLabsClientSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,98 @@

using System;

namespace ElevenLabs
namespace ElevenLabs;

public sealed class ElevenLabsClientSettings
{
public sealed class ElevenLabsClientSettings
internal const string HttpProtocol = "http://";
internal const string HttpsProtocol = "https://";
internal const string WsProtocol = "ws://";
internal const string WssProtocol = "wss://";
internal const string DefaultApiVersion = "v1";
internal const string ElevenLabsDomain = "api.elevenlabs.io";

/// <summary>
/// Creates a new instance of <see cref="ElevenLabsClientSettings" /> for use with ElevenLabs API.
/// </summary>
public ElevenLabsClientSettings()
{
Domain = ElevenLabsDomain;
ApiVersion = DefaultApiVersion;
Protocol = HttpsProtocol;
WebSocketProtocol = WssProtocol;
BaseRequest = $"/{ApiVersion}/";
BaseRequestUrlFormat = $"{Protocol}{Domain}{BaseRequest}{{0}}";
BaseRequestWebSocketUrlFormat = $"{WebSocketProtocol}{Domain}{BaseRequest}{{0}}";
}

/// <summary>
/// Creates a new instance of <see cref="ElevenLabsClientSettings" /> for use with ElevenLabs API.
/// </summary>
/// <param name="domain">Base api domain. Starts with https or wss.</param>
/// <param name="apiVersion">The version of the ElevenLabs api you want to use.</param>
public ElevenLabsClientSettings(string domain, string apiVersion = DefaultApiVersion)
{
internal const string Https = "https://";
internal const string DefaultApiVersion = "v1";
internal const string ElevenLabsDomain = "api.elevenlabs.io";

/// <summary>
/// Creates a new instance of <see cref="ElevenLabsClientSettings"/> for use with ElevenLabs API.
/// </summary>
public ElevenLabsClientSettings()
if (string.IsNullOrWhiteSpace(domain))
{
Domain = ElevenLabsDomain;
ApiVersion = "v1";
BaseRequest = $"/{ApiVersion}/";
BaseRequestUrlFormat = $"{Https}{Domain}{BaseRequest}{{0}}";
domain = ElevenLabsDomain;
}

/// <summary>
/// Creates a new instance of <see cref="ElevenLabsClientSettings"/> for use with ElevenLabs API.
/// </summary>
/// <param name="domain">Base api domain.</param>
/// <param name="apiVersion">The version of the ElevenLabs api you want to use.</param>
public ElevenLabsClientSettings(string domain, string apiVersion = DefaultApiVersion)
if (!domain.Contains('.') &&
!domain.Contains(':'))
{
if (string.IsNullOrWhiteSpace(domain))
{
domain = ElevenLabsDomain;
}
throw new ArgumentException(
$"You're attempting to pass a \"resourceName\" parameter to \"{nameof(domain)}\". Please specify \"resourceName:\" for this parameter in constructor.");
}

if (!domain.Contains('.') &&
!domain.Contains(':'))
// extract anything before the :// to split the domain and protocol
var splitDomain = domain.Split("://", StringSplitOptions.RemoveEmptyEntries);
if (splitDomain.Length == 2)
{
Protocol = splitDomain[0];
// if the protocol is not https or http, throw an exception
if (Protocol != HttpsProtocol &&
Protocol != HttpProtocol)
{
throw new ArgumentException($"You're attempting to pass a \"resourceName\" parameter to \"{nameof(domain)}\". Please specify \"resourceName:\" for this parameter in constructor.");
throw new ArgumentException(
$"The protocol \"{Protocol}\" is not supported. Please use \"{HttpsProtocol}\" or \"{HttpProtocol}\".");
}

if (string.IsNullOrWhiteSpace(apiVersion))
{
apiVersion = DefaultApiVersion;
}
WebSocketProtocol = Protocol == HttpsProtocol ? WssProtocol : WsProtocol;
Domain = splitDomain[1];
}
else
{
Protocol = HttpsProtocol;
WebSocketProtocol = WssProtocol;
Domain = domain;
}

Domain = domain.Contains("http") ? domain : $"{Https}{domain}";
ApiVersion = apiVersion;
BaseRequest = $"/{ApiVersion}/";
BaseRequestUrlFormat = $"{Domain}{BaseRequest}{{0}}";
if (string.IsNullOrWhiteSpace(apiVersion))
{
apiVersion = DefaultApiVersion;
}

public string Domain { get; }
Domain = domain;
ApiVersion = apiVersion;
BaseRequest = $"/{ApiVersion}/";
BaseRequestUrlFormat = $"{Protocol}{Domain}{BaseRequest}{{0}}";
BaseRequestWebSocketUrlFormat = $"{WebSocketProtocol}{Domain}{BaseRequest}{{0}}";
}

public string Protocol { get; }

public string ApiVersion { get; }
public string WebSocketProtocol { get; }

public string BaseRequest { get; }
public string Domain { get; }

public string BaseRequestUrlFormat { get; }
public string ApiVersion { get; }

public static ElevenLabsClientSettings Default { get; } = new();
}
}
public string BaseRequest { get; }

public string BaseRequestUrlFormat { get; }

public string BaseRequestWebSocketUrlFormat { get; }

public static ElevenLabsClientSettings Default { get; } = new();
}
81 changes: 46 additions & 35 deletions ElevenLabs-DotNet/Common/ElevenLabsBaseEndPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,58 @@
using System.Collections.Generic;
using System.Linq;

namespace ElevenLabs
namespace ElevenLabs;

public abstract class ElevenLabsBaseEndPoint
{
public abstract class ElevenLabsBaseEndPoint
internal ElevenLabsBaseEndPoint(ElevenLabsClient client) => this.client = client;

// ReSharper disable once InconsistentNaming
protected readonly ElevenLabsClient client;

/// <summary>
/// The root endpoint address.
/// </summary>
protected abstract string Root { get; }

/// <summary>
/// Gets the full formatted url for the API endpoint.
/// </summary>
/// <param name="endpoint">The endpoint url.</param>
/// <param name="queryParameters">Optional, parameters to add to the endpoint.</param>
protected string GetUrl(string endpoint = "", Dictionary<string, string> queryParameters = null)
{
internal ElevenLabsBaseEndPoint(ElevenLabsClient client) => this.client = client;

// ReSharper disable once InconsistentNaming
protected readonly ElevenLabsClient client;

/// <summary>
/// The root endpoint address.
/// </summary>
protected abstract string Root { get; }

/// <summary>
/// Gets the full formatted url for the API endpoint.
/// </summary>
/// <param name="endpoint">The endpoint url.</param>
/// <param name="queryParameters">Optional, parameters to add to the endpoint.</param>
protected string GetUrl(string endpoint = "", Dictionary<string, string> queryParameters = null)
{
var result = string.Format(client.ElevenLabsClientSettings.BaseRequestUrlFormat, $"{Root}{endpoint}");

if (queryParameters is { Count: not 0 })
{
result += $"?{string.Join('&', queryParameters.Select(parameter => $"{parameter.Key}={parameter.Value}"))}";
}
var result = string.Format(client.ElevenLabsClientSettings.BaseRequestUrlFormat, $"{Root}{endpoint}");

return result;
if (queryParameters is { Count: not 0 })
{
result += $"?{string.Join('&', queryParameters.Select(parameter => $"{parameter.Key}={parameter.Value}"))}";
}

private bool enableDebug;
return result;
}

protected string GetWebSocketUrl(string endpoint = "", Dictionary<string, string> queryParameters = null)
{
var result = string.Format(client.ElevenLabsClientSettings.BaseRequestWebSocketUrlFormat, $"{Root}{endpoint}");

/// <summary>
/// Enables or disables the logging of all http responses of header and body information for this endpoint.<br/>
/// WARNING! Enabling this in your production build, could potentially leak sensitive information!
/// </summary>
public bool EnableDebug
if (queryParameters is { Count: not 0 })
{
get => enableDebug || client.EnableDebug;
set => enableDebug = value;
result += $"?{string.Join('&', queryParameters.Select(parameter => $"{parameter.Key}={parameter.Value}"))}";
}

return result;
}

private bool enableDebug;

/// <summary>
/// Enables or disables the logging of all http responses of header and body information for this endpoint.<br />
/// WARNING! Enabling this in your production build, could potentially leak sensitive information!
/// </summary>
public bool EnableDebug
{
get => enableDebug || client.EnableDebug;
set => enableDebug = value;
}
}
}
70 changes: 53 additions & 17 deletions ElevenLabs-DotNet/ElevenLabsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using ElevenLabs.Voices;
using System;
using System.Net.Http;
using System.Net.WebSockets;
using System.Security.Authentication;
using System.Text.Json;
using System.Text.Json.Serialization;
Expand All @@ -19,31 +20,42 @@ namespace ElevenLabs
public sealed class ElevenLabsClient : IDisposable
{
/// <summary>
/// Creates a new client for the Eleven Labs API, handling auth and allowing for access to various API endpoints.
/// Creates a new client for the Eleven Labs API, handling auth and allowing for access to various API endpoints.
/// </summary>
/// <param name="authentication">The API authentication information to use for API calls,
/// or <see langword="null"/> to attempt to use the <see cref="ElevenLabsAuthentication.Default"/>,
/// potentially loading from environment vars or from a config file.
/// <param name="authentication">
/// The API authentication information to use for API calls,
/// or <see langword="null" /> to attempt to use the <see cref="ElevenLabsAuthentication.Default" />,
/// potentially loading from environment vars or from a config file.
/// </param>
/// <param name="settings">
/// Optional, <see cref="ElevenLabsClientSettings"/> for specifying a proxy domain.
/// Optional, <see cref="ElevenLabsClientSettings" /> for specifying a proxy domain.
/// </param>
/// <param name="httpClient">Optional, <see cref="HttpClient"/>.</param>
/// <param name="httpClient">Optional, <see cref="HttpClient" />.</param>
/// <param name="clientWebSocketSpawner">Optional, to create custom versions of <see cref="ClientWebSocket" />.</param>
/// <exception cref="AuthenticationException">Raised when authentication details are missing or invalid.</exception>
/// <see cref="ElevenLabsClient"/> implements <see cref="IDisposable"/> to manage the lifecycle of the resources it uses, including <see cref="HttpClient"/>.
/// <see cref="ElevenLabsClient" />
/// implements
/// <see cref="IDisposable" />
/// to manage the lifecycle of the resources it uses, including
/// <see cref="HttpClient" />
/// .
/// <remarks>
/// When you initialize <see cref="ElevenLabsClient"/>, it will create an internal <see cref="HttpClient"/> instance if one is not provided.
/// This internal HttpClient is disposed of when ElevenLabsClient is disposed of.
/// If you provide an external HttpClient instance to ElevenLabsClient, you are responsible for managing its disposal.
/// When you initialize <see cref="ElevenLabsClient" />, it will create an internal <see cref="HttpClient" /> instance
/// if one is not provided.
/// This internal HttpClient is disposed of when ElevenLabsClient is disposed of.
/// If you provide an external HttpClient instance to ElevenLabsClient, you are responsible for managing its disposal.
/// </remarks>
public ElevenLabsClient(ElevenLabsAuthentication authentication = null, ElevenLabsClientSettings settings = null, HttpClient httpClient = null)
public ElevenLabsClient(ElevenLabsAuthentication authentication = null,
ElevenLabsClientSettings settings = null, HttpClient httpClient = null,
Func<ClientWebSocket> clientWebSocketSpawner = null)
{
ElevenLabsAuthentication = authentication ?? ElevenLabsAuthentication.Default;
ElevenLabsClientSettings = settings ?? ElevenLabsClientSettings.Default;

if (string.IsNullOrWhiteSpace(ElevenLabsAuthentication?.ApiKey))
{
throw new AuthenticationException("You must provide API authentication. Please refer to https://github.com/RageAgainstThePixel/ElevenLabs-DotNet#authentication for details.");
throw new AuthenticationException(
"You must provide API authentication. Please refer to https://github.com/RageAgainstThePixel/ElevenLabs-DotNet#authentication for details.");
}

if (httpClient == null)
Expand All @@ -62,17 +74,31 @@ public ElevenLabsClient(ElevenLabsAuthentication authentication = null, ElevenLa
Client.DefaultRequestHeaders.Add("User-Agent", "ElevenLabs-DotNet");
Client.DefaultRequestHeaders.Add("xi-api-key", ElevenLabsAuthentication.ApiKey);

this.clientWebSocketSpawner = clientWebSocketSpawner;
WebSocketClient = clientWebSocketSpawner == null ? new ClientWebSocket() : clientWebSocketSpawner();
WebSocketClient.Options.SetRequestHeader("User-Agent", "ElevenLabs-DotNet");
WebSocketClient.Options.SetRequestHeader("xi-api-key", ElevenLabsAuthentication.ApiKey);

UserEndpoint = new UserEndpoint(this);
VoicesEndpoint = new VoicesEndpoint(this);
SharedVoicesEndpoint = new SharedVoicesEndpoint(this);
ModelsEndpoint = new ModelsEndpoint(this);
HistoryEndpoint = new HistoryEndpoint(this);
TextToSpeechEndpoint = new TextToSpeechEndpoint(this);
TextToSpeechWebSocketEndpoint = new TextToSpeechWebSocketEndpoint(this);
VoiceGenerationEndpoint = new VoiceGenerationEndpoint(this);
SoundGenerationEndpoint = new SoundGenerationEndpoint(this);
DubbingEndpoint = new DubbingEndpoint(this);
}

public void ReinitializeWebSocketClient()
{
WebSocketClient.Dispose();
WebSocketClient = clientWebSocketSpawner == null ? new ClientWebSocket() : clientWebSocketSpawner();
WebSocketClient.Options.SetRequestHeader("User-Agent", "ElevenLabs-DotNet");
WebSocketClient.Options.SetRequestHeader("xi-api-key", ElevenLabsAuthentication.ApiKey);
}

~ElevenLabsClient()
{
Dispose(false);
Expand All @@ -97,6 +123,7 @@ private void Dispose(bool disposing)
Client?.Dispose();
}

WebSocketClient?.Dispose();
isDisposed = true;
}
}
Expand All @@ -105,26 +132,33 @@ private void Dispose(bool disposing)

private bool isCustomClient;

private Func<ClientWebSocket> clientWebSocketSpawner;

/// <summary>
/// <see cref="HttpClient"/> to use when making calls to the API.
/// <see cref="HttpClient" /> to use when making calls to the API.
/// </summary>
internal HttpClient Client { get; }

/// <summary>
/// The <see cref="JsonSerializationOptions"/> to use when making calls to the API.
/// <see cref="ClientWebSocket" /> to use when making calls to the API.
/// </summary>
internal ClientWebSocket WebSocketClient { get; private set; }

/// <summary>
/// The <see cref="JsonSerializationOptions" /> to use when making calls to the API.
/// </summary>
internal static JsonSerializerOptions JsonSerializationOptions { get; } = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

/// <summary>
/// Enables or disables debugging for all endpoints.
/// Enables or disables debugging for all endpoints.
/// </summary>
public bool EnableDebug { get; set; }

/// <summary>
/// The API authentication information to use for API calls
/// The API authentication information to use for API calls
/// </summary>
public ElevenLabsAuthentication ElevenLabsAuthentication { get; }

Expand All @@ -142,10 +176,12 @@ private void Dispose(bool disposing)

public TextToSpeechEndpoint TextToSpeechEndpoint { get; }

public TextToSpeechWebSocketEndpoint TextToSpeechWebSocketEndpoint { get; }

public VoiceGenerationEndpoint VoiceGenerationEndpoint { get; }

public SoundGenerationEndpoint SoundGenerationEndpoint { get; }

public DubbingEndpoint DubbingEndpoint { get; }
}
}
}
Loading