diff --git a/ElevenLabs-DotNet-Proxy/ElevenLabs-DotNet-Proxy.csproj b/ElevenLabs-DotNet-Proxy/ElevenLabs-DotNet-Proxy.csproj
index 44aba86..87f53ab 100644
--- a/ElevenLabs-DotNet-Proxy/ElevenLabs-DotNet-Proxy.csproj
+++ b/ElevenLabs-DotNet-Proxy/ElevenLabs-DotNet-Proxy.csproj
@@ -1,48 +1,48 @@
-
- net6.0
- disable
- disable
- True
- false
- false
- Stephen Hodgson
- ElevenLabs-DotNet-Proxy
- A simple Proxy API gateway for ElevenLabs-DotNet to make authenticated requests from a front end application without exposing your API keys.
- 2023
- https://github.com/RageAgainstThePixel/ElevenLabs-DotNet
- https://github.com/RageAgainstThePixel/ElevenLabs-DotNet
- ElevenLabs, AI, ML, API, api-proxy, proxy, gateway
- ElevenLabs API Proxy
- ElevenLabs-DotNet-Proxy
- 1.0.1
- ElevenLabs.Proxy
- Initial Release!
- True
- false
- Readme.md
- True
- ElevenLabsIcon.png
- LICENSE
-
-
-
-
-
-
-
- True
- \
-
-
- True
- \
-
-
-
-
- True
- \
-
-
+
+ net6.0
+ disable
+ disable
+ True
+ false
+ false
+ Stephen Hodgson
+ ElevenLabs-DotNet-Proxy
+ A simple Proxy API gateway for ElevenLabs-DotNet to make authenticated requests from a front end application without exposing your API keys.
+ 2024
+ https://github.com/RageAgainstThePixel/ElevenLabs-DotNet
+ https://github.com/RageAgainstThePixel/ElevenLabs-DotNet
+ ElevenLabs, AI, ML, API, api-proxy, proxy, gateway
+ ElevenLabs API Proxy
+ ElevenLabs-DotNet-Proxy
+ 1.2.0
+ ElevenLabs.Proxy
+ Initial Release!
+ True
+ false
+ Readme.md
+ True
+ ElevenLabsIcon.png
+ LICENSE
+
+
+
+
+
+
+
+ True
+ \
+
+
+ True
+ \
+
+
+
+
+ True
+ \
+
+
diff --git a/ElevenLabs-DotNet-Proxy/Proxy/AbstractAuthenticationFilter.cs b/ElevenLabs-DotNet-Proxy/Proxy/AbstractAuthenticationFilter.cs
index 6323cf3..37a4f12 100644
--- a/ElevenLabs-DotNet-Proxy/Proxy/AbstractAuthenticationFilter.cs
+++ b/ElevenLabs-DotNet-Proxy/Proxy/AbstractAuthenticationFilter.cs
@@ -1,5 +1,6 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace ElevenLabs.Proxy
@@ -9,5 +10,8 @@ public abstract class AbstractAuthenticationFilter : IAuthenticationFilter
{
///
public abstract void ValidateAuthentication(IHeaderDictionary request);
+
+ ///
+ public abstract Task ValidateAuthenticationAsync(IHeaderDictionary request);
}
}
diff --git a/ElevenLabs-DotNet-Proxy/Proxy/ElevenLabsProxyStartup.cs b/ElevenLabs-DotNet-Proxy/Proxy/ElevenLabsProxyStartup.cs
index af4f5a7..12a14e4 100644
--- a/ElevenLabs-DotNet-Proxy/Proxy/ElevenLabsProxyStartup.cs
+++ b/ElevenLabs-DotNet-Proxy/Proxy/ElevenLabsProxyStartup.cs
@@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Net.Http.Headers;
@@ -26,7 +27,7 @@ public class ElevenLabsProxyStartup
private IAuthenticationFilter authenticationFilter;
// Copied from https://github.com/microsoft/reverse-proxy/blob/51d797986b1fea03500a1ad173d13a1176fb5552/src/ReverseProxy/Forwarder/RequestUtilities.cs#L61-L83
- private static readonly HashSet ExcludedHeaders = new HashSet()
+ private static readonly HashSet excludedHeaders = new()
{
HeaderNames.Connection,
HeaderNames.TransferEncoding,
@@ -49,7 +50,12 @@ public class ElevenLabsProxyStartup
#endif
};
- public void ConfigureServices(IServiceCollection services) { }
+ ///
+ /// Configures the and services.
+ ///
+ ///
+ public void ConfigureServices(IServiceCollection services)
+ => SetupServices(services.BuildServiceProvider());
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
@@ -58,8 +64,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseDeveloperExceptionPage();
}
- elevenLabsClient = app.ApplicationServices.GetRequiredService();
- authenticationFilter = app.ApplicationServices.GetRequiredService();
+ SetupServices(app.ApplicationServices);
app.UseHttpsRedirection();
app.UseRouting();
@@ -77,25 +82,43 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
/// Startup args.
/// with configured and .
public static IHost CreateDefaultHost(string[] args, ElevenLabsClient elevenLabsClient) where T : class, IAuthenticationFilter
- {
- return Host.CreateDefaultBuilder(args)
+ => Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup();
- webBuilder.ConfigureKestrel(options =>
- {
- options.AllowSynchronousIO = false;
- options.Limits.MinRequestBodyDataRate = null;
- options.Limits.MinResponseDataRate = null;
- options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(10);
- options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(2);
- });
+ webBuilder.ConfigureKestrel(ConfigureKestrel);
})
.ConfigureServices(services =>
{
services.AddSingleton(elevenLabsClient);
services.AddSingleton();
}).Build();
+
+ public static WebApplication CreateWebApplication(string[] args, ElevenLabsClient elevenLabsClient) where T : class, IAuthenticationFilter
+ {
+ var builder = WebApplication.CreateBuilder(args);
+ builder.WebHost.ConfigureKestrel(ConfigureKestrel);
+ builder.Services.AddSingleton(elevenLabsClient);
+ builder.Services.AddSingleton();
+ var app = builder.Build();
+ var startup = new ElevenLabsProxyStartup();
+ startup.Configure(app, app.Environment);
+ return app;
+ }
+
+ private static void ConfigureKestrel(KestrelServerOptions options)
+ {
+ options.AllowSynchronousIO = false;
+ options.Limits.MinRequestBodyDataRate = null;
+ options.Limits.MinResponseDataRate = null;
+ options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(10);
+ options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(2);
+ }
+
+ private void SetupServices(IServiceProvider serviceProvider)
+ {
+ elevenLabsClient = serviceProvider.GetRequiredService();
+ authenticationFilter = serviceProvider.GetRequiredService();
}
private static async Task HealthEndpoint(HttpContext context)
@@ -115,31 +138,34 @@ private async Task HandleRequest(HttpContext httpContext, string endpoint)
{
try
{
+ // ReSharper disable once MethodHasAsyncOverload
+ // just in case either method is implemented we call it twice.
authenticationFilter.ValidateAuthentication(httpContext.Request.Headers);
+ await authenticationFilter.ValidateAuthenticationAsync(httpContext.Request.Headers);
var method = new HttpMethod(httpContext.Request.Method);
var uri = new Uri(string.Format(elevenLabsClient.ElevenLabsClientSettings.BaseRequestUrlFormat, $"{endpoint}{httpContext.Request.QueryString}"));
- var elevenLabsRequest = new HttpRequestMessage(method, uri);
+ using var request = new HttpRequestMessage(method, uri);
- elevenLabsRequest.Content = new StreamContent(httpContext.Request.Body);
+ request.Content = new StreamContent(httpContext.Request.Body);
if (httpContext.Request.ContentType != null)
{
- elevenLabsRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(httpContext.Request.ContentType);
+ request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(httpContext.Request.ContentType);
}
- var proxyResponse = await elevenLabsClient.Client.SendAsync(elevenLabsRequest, HttpCompletionOption.ResponseHeadersRead);
+ var proxyResponse = await elevenLabsClient.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
httpContext.Response.StatusCode = (int)proxyResponse.StatusCode;
foreach (var (key, value) in proxyResponse.Headers)
{
- if (ExcludedHeaders.Contains(key)) { continue; }
+ if (excludedHeaders.Contains(key)) { continue; }
httpContext.Response.Headers[key] = value.ToArray();
}
foreach (var (key, value) in proxyResponse.Content.Headers)
{
- if (ExcludedHeaders.Contains(key)) { continue; }
+ if (excludedHeaders.Contains(key)) { continue; }
httpContext.Response.Headers[key] = value.ToArray();
}
diff --git a/ElevenLabs-DotNet-Proxy/Proxy/IAuthenticationFilter.cs b/ElevenLabs-DotNet-Proxy/Proxy/IAuthenticationFilter.cs
index d568b07..2b692e8 100644
--- a/ElevenLabs-DotNet-Proxy/Proxy/IAuthenticationFilter.cs
+++ b/ElevenLabs-DotNet-Proxy/Proxy/IAuthenticationFilter.cs
@@ -1,7 +1,8 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.
-using System.Security.Authentication;
using Microsoft.AspNetCore.Http;
+using System.Security.Authentication;
+using System.Threading.Tasks;
namespace ElevenLabs.Proxy
{
@@ -17,5 +18,13 @@ public interface IAuthenticationFilter
///
///
void ValidateAuthentication(IHeaderDictionary request);
+
+ ///
+ /// Checks the headers for your user issued token.
+ /// If it's not valid, then throw .
+ ///
+ ///
+ ///
+ Task ValidateAuthenticationAsync(IHeaderDictionary request);
}
}
diff --git a/ElevenLabs-DotNet-Proxy/Readme.md b/ElevenLabs-DotNet-Proxy/Readme.md
index 2319007..8aa3ae8 100644
--- a/ElevenLabs-DotNet-Proxy/Readme.md
+++ b/ElevenLabs-DotNet-Proxy/Readme.md
@@ -51,7 +51,7 @@ In this example, we demonstrate how to set up and use `ElevenLabsProxyStartup` i
- Powershell install: `Install-Package ElevenLabs-DotNet-Proxy`
- Manually editing .csproj: ``
3. Create a new class that inherits from `AbstractAuthenticationFilter` and override the `ValidateAuthentication` method. This will implement the `IAuthenticationFilter` that you will use to check user session token against your internal server.
-4. In `Program.cs`, create a new proxy web application by calling `ElevenLabsProxyStartup.CreateDefaultHost` method, passing your custom `AuthenticationFilter` as a type argument.
+4. In `Program.cs`, create a new proxy web application by calling `ElevenLabsProxyStartup.CreateWebApplication` method, passing your custom `AuthenticationFilter` as a type argument.
5. Create `ElevenLabsAuthentication` and `ElevenLabsClientSettings` as you would normally with your API keys, org id, or Azure settings.
```csharp
@@ -63,7 +63,19 @@ public partial class Program
{
// You will need to implement your own class to properly test
// custom issued tokens you've setup for your end users.
- if (!request["xi-api-key"].ToString().Contains(userToken))
+ if (!request["xi-api-key"].ToString().Contains(TestUserToken))
+ {
+ throw new AuthenticationException("User is not authorized");
+ }
+ }
+
+ public override async Task ValidateAuthenticationAsync(IHeaderDictionary request)
+ {
+ await Task.CompletedTask; // remote resource call
+
+ // You will need to implement your own class to properly test
+ // custom issued tokens you've setup for your end users.
+ if (!request["xi-api-key"].ToString().Contains(TestUserToken))
{
throw new AuthenticationException("User is not authorized");
}
@@ -72,9 +84,9 @@ public partial class Program
public static void Main(string[] args)
{
- var client = new ElevenLabsClient();
- var proxy = ElevenLabsProxyStartup.CreateDefaultHost(args, client);
- proxy.Run();
+ var auth = ElevenLabsAuthentication.LoadFromEnv();
+ var client = new ElevenLabsClient(auth);
+ ElevenLabsProxyStartup.CreateWebApplication(args, client).Run();
}
}
```
diff --git a/ElevenLabs-DotNet-Tests-Proxy/Program.cs b/ElevenLabs-DotNet-Tests-Proxy/Program.cs
index d959642..37d5d1d 100644
--- a/ElevenLabs-DotNet-Tests-Proxy/Program.cs
+++ b/ElevenLabs-DotNet-Tests-Proxy/Program.cs
@@ -2,8 +2,8 @@
using ElevenLabs.Proxy;
using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Hosting;
using System.Security.Authentication;
+using System.Threading.Tasks;
namespace ElevenLabs.Tests.Proxy
{
@@ -27,14 +27,25 @@ public override void ValidateAuthentication(IHeaderDictionary request)
throw new AuthenticationException("User is not authorized");
}
}
+
+ public override async Task ValidateAuthenticationAsync(IHeaderDictionary request)
+ {
+ await Task.CompletedTask; // remote resource call
+
+ // You will need to implement your own class to properly test
+ // custom issued tokens you've setup for your end users.
+ if (!request["xi-api-key"].ToString().Contains(TestUserToken))
+ {
+ throw new AuthenticationException("User is not authorized");
+ }
+ }
}
public static void Main(string[] args)
{
var auth = ElevenLabsAuthentication.LoadFromEnv();
var client = new ElevenLabsClient(auth);
- var proxy = ElevenLabsProxyStartup.CreateDefaultHost(args, client);
- proxy.Run();
+ ElevenLabsProxyStartup.CreateWebApplication(args, client).Run();
}
}
}
\ No newline at end of file
diff --git a/ElevenLabs-DotNet-Tests/Test_Fixture_02_VoicesEndpoint.cs b/ElevenLabs-DotNet-Tests/Test_Fixture_02_VoicesEndpoint.cs
index af1d3be..c577290 100644
--- a/ElevenLabs-DotNet-Tests/Test_Fixture_02_VoicesEndpoint.cs
+++ b/ElevenLabs-DotNet-Tests/Test_Fixture_02_VoicesEndpoint.cs
@@ -86,7 +86,43 @@ public async Task Test_05_AddVoice()
}
[Test]
- public async Task Test_06_EditVoice()
+ public async Task Test_06_AddVoiceFromByteArray()
+ {
+ Assert.NotNull(ElevenLabsClient.VoicesEndpoint);
+ var testLabels = new Dictionary
+ {
+ { "accent", "american" }
+ };
+ var clipPath = Path.GetFullPath("../../../Assets/test_sample_01.ogg");
+ byte[] clipData = await File.ReadAllBytesAsync(clipPath);
+ var result = await ElevenLabsClient.VoicesEndpoint.AddVoiceAsync("Test Voice", new[] { clipData }, testLabels);
+ Assert.NotNull(result);
+ Console.WriteLine($"{result.Name}");
+ Assert.IsNotEmpty(result.Samples);
+ }
+
+
+ [Test]
+ public async Task Test_07_AddVoiceFromStream()
+ {
+ Assert.NotNull(ElevenLabsClient.VoicesEndpoint);
+ var testLabels = new Dictionary
+ {
+ { "accent", "american" }
+ };
+ var clipPath = Path.GetFullPath("../../../Assets/test_sample_01.ogg");
+
+ using (FileStream fs = File.OpenRead(clipPath))
+ {
+ var result = await ElevenLabsClient.VoicesEndpoint.AddVoiceAsync("Test Voice", new[] { fs }, testLabels);
+ Assert.NotNull(result);
+ Console.WriteLine($"{result.Name}");
+ Assert.IsNotEmpty(result.Samples);
+ }
+ }
+
+ [Test]
+ public async Task Test_08_EditVoice()
{
Assert.NotNull(ElevenLabsClient.VoicesEndpoint);
var results = await ElevenLabsClient.VoicesEndpoint.GetAllVoicesAsync();
@@ -106,7 +142,7 @@ public async Task Test_06_EditVoice()
}
[Test]
- public async Task Test_07_GetVoiceSample()
+ public async Task Test_09_GetVoiceSample()
{
Assert.NotNull(ElevenLabsClient.VoicesEndpoint);
var results = await ElevenLabsClient.VoicesEndpoint.GetAllVoicesAsync();
@@ -124,7 +160,7 @@ public async Task Test_07_GetVoiceSample()
}
[Test]
- public async Task Test_08_DeleteVoiceSample()
+ public async Task Test_10_DeleteVoiceSample()
{
Assert.NotNull(ElevenLabsClient.VoicesEndpoint);
var results = await ElevenLabsClient.VoicesEndpoint.GetAllVoicesAsync();
@@ -143,7 +179,7 @@ public async Task Test_08_DeleteVoiceSample()
}
[Test]
- public async Task Test_09_DeleteVoice()
+ public async Task Test_11_DeleteVoice()
{
Assert.NotNull(ElevenLabsClient.VoicesEndpoint);
var results = await ElevenLabsClient.VoicesEndpoint.GetAllVoicesAsync();
diff --git a/ElevenLabs-DotNet/Authentication/ElevenLabsAuthentication.cs b/ElevenLabs-DotNet/Authentication/ElevenLabsAuthentication.cs
index d33bc86..18ecbf1 100644
--- a/ElevenLabs-DotNet/Authentication/ElevenLabsAuthentication.cs
+++ b/ElevenLabs-DotNet/Authentication/ElevenLabsAuthentication.cs
@@ -51,9 +51,9 @@ public static ElevenLabsAuthentication Default
return cachedDefault;
}
- var auth = (LoadFromDirectory()) ??
- LoadFromDirectory(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)) ??
- LoadFromEnv();
+ var auth = LoadFromDirectory() ??
+ LoadFromDirectory(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)) ??
+ LoadFromEnv();
cachedDefault = auth;
return auth;
}
diff --git a/ElevenLabs-DotNet/Authentication/ElevenLabsClientSettings.cs b/ElevenLabs-DotNet/Authentication/ElevenLabsClientSettings.cs
index 715e202..c876e39 100644
--- a/ElevenLabs-DotNet/Authentication/ElevenLabsClientSettings.cs
+++ b/ElevenLabs-DotNet/Authentication/ElevenLabsClientSettings.cs
@@ -6,8 +6,9 @@ namespace ElevenLabs
{
public sealed class ElevenLabsClientSettings
{
- internal const string ElevenLabsDomain = "api.elevenlabs.io";
+ internal const string Https = "https://";
internal const string DefaultApiVersion = "v1";
+ internal const string ElevenLabsDomain = "api.elevenlabs.io";
///
/// Creates a new instance of for use with ElevenLabs API.
@@ -17,7 +18,7 @@ public ElevenLabsClientSettings()
Domain = ElevenLabsDomain;
ApiVersion = "v1";
BaseRequest = $"/{ApiVersion}/";
- BaseRequestUrlFormat = $"https://{Domain}{BaseRequest}{{0}}";
+ BaseRequestUrlFormat = $"{Https}{Domain}{BaseRequest}{{0}}";
}
///
@@ -32,8 +33,8 @@ public ElevenLabsClientSettings(string domain, string apiVersion = DefaultApiVer
domain = ElevenLabsDomain;
}
- if (!domain.Contains(".") &&
- !domain.Contains(":"))
+ if (!domain.Contains('.') &&
+ !domain.Contains(':'))
{
throw new ArgumentException($"You're attempting to pass a \"resourceName\" parameter to \"{nameof(domain)}\". Please specify \"resourceName:\" for this parameter in constructor.");
}
@@ -43,10 +44,10 @@ public ElevenLabsClientSettings(string domain, string apiVersion = DefaultApiVer
apiVersion = DefaultApiVersion;
}
- Domain = domain;
+ Domain = domain.Contains("http") ? domain : $"{Https}{domain}";
ApiVersion = apiVersion;
BaseRequest = $"/{ApiVersion}/";
- BaseRequestUrlFormat = $"https://{Domain}{BaseRequest}{{0}}";
+ BaseRequestUrlFormat = $"{Domain}{BaseRequest}{{0}}";
}
public string Domain { get; }
@@ -57,6 +58,6 @@ public ElevenLabsClientSettings(string domain, string apiVersion = DefaultApiVer
public string BaseRequestUrlFormat { get; }
- public static ElevenLabsClientSettings Default { get; } = new ElevenLabsClientSettings();
+ public static ElevenLabsClientSettings Default { get; } = new();
}
}
diff --git a/ElevenLabs-DotNet/ElevenLabs-DotNet.csproj b/ElevenLabs-DotNet/ElevenLabs-DotNet.csproj
index 831b40f..fb235ad 100644
--- a/ElevenLabs-DotNet/ElevenLabs-DotNet.csproj
+++ b/ElevenLabs-DotNet/ElevenLabs-DotNet.csproj
@@ -12,10 +12,15 @@
Stephen Hodgson
ElevenLabs-DotNet
RageAgainstThePixel
- 2023
+ 2024
ElevenLabs, AI, ML, Voice, TTS
- 2.1.1
- Version 2.1.1
+ 2.2.0
+ Version 2.2.0
+- Changed ElevenLabsClient to be IDisposable
+ - The ElevenLabsClient must now be disposed if you do not pass your own HttpClient
+- Updated ElevenLabsClientSettings to accept custom domains
+- Added filesystemless overloads for uploading audio clips
+Version 2.1.1
- Added VoicesEndpoint.GetAllVoicesAsync overload that allows skipping downloading the voice settings
Version 2.1.0
- Added ElevenLabsClient.EnableDebug option to enable and disable for all endpoints
diff --git a/ElevenLabs-DotNet/ElevenLabsClient.cs b/ElevenLabs-DotNet/ElevenLabsClient.cs
index aa33a62..a064398 100644
--- a/ElevenLabs-DotNet/ElevenLabsClient.cs
+++ b/ElevenLabs-DotNet/ElevenLabsClient.cs
@@ -6,6 +6,7 @@
using ElevenLabs.User;
using ElevenLabs.VoiceGeneration;
using ElevenLabs.Voices;
+using System;
using System.Net.Http;
using System.Security.Authentication;
using System.Text.Json;
@@ -13,7 +14,7 @@
namespace ElevenLabs
{
- public sealed class ElevenLabsClient
+ public sealed class ElevenLabsClient : IDisposable
{
///
/// Creates a new client for the Eleven Labs API, handling auth and allowing for access to various API endpoints.
@@ -27,6 +28,12 @@ public sealed class ElevenLabsClient
///
/// Optional, .
/// Raised when authentication details are missing or invalid.
+ /// implements to manage the lifecycle of the resources it uses, including .
+ ///
+ /// When you initialize , it will create an internal 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.
+ ///
public ElevenLabsClient(ElevenLabsAuthentication elevenLabsAuthentication = null, ElevenLabsClientSettings clientSettings = null, HttpClient httpClient = null)
{
ElevenLabsAuthentication = elevenLabsAuthentication ?? ElevenLabsAuthentication.Default;
@@ -37,7 +44,19 @@ public ElevenLabsClient(ElevenLabsAuthentication elevenLabsAuthentication = null
throw new AuthenticationException("You must provide API authentication. Please refer to https://github.com/RageAgainstThePixel/ElevenLabs-DotNet#authentication for details.");
}
- Client = httpClient ?? new HttpClient();
+ if (httpClient == null)
+ {
+ httpClient = new HttpClient(new SocketsHttpHandler
+ {
+ PooledConnectionLifetime = TimeSpan.FromMinutes(15)
+ });
+ }
+ else
+ {
+ isCustomClient = true;
+ }
+
+ Client = httpClient;
Client.DefaultRequestHeaders.Add("User-Agent", "ElevenLabs-DotNet");
Client.DefaultRequestHeaders.Add("xi-api-key", ElevenLabsAuthentication.ApiKey);
@@ -49,6 +68,38 @@ public ElevenLabsClient(ElevenLabsAuthentication elevenLabsAuthentication = null
VoiceGenerationEndpoint = new VoiceGenerationEndpoint(this);
}
+ ~ElevenLabsClient()
+ {
+ Dispose(false);
+ }
+
+ #region IDisposable
+
+ private bool isDisposed;
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (!isDisposed && disposing)
+ {
+ if (!isCustomClient)
+ {
+ Client?.Dispose();
+ }
+
+ isDisposed = true;
+ }
+ }
+
+ #endregion IDisposable
+
+ private bool isCustomClient;
+
///
/// to use when making calls to the API.
///
@@ -57,7 +108,7 @@ public ElevenLabsClient(ElevenLabsAuthentication elevenLabsAuthentication = null
///
/// The to use when making calls to the API.
///
- internal static JsonSerializerOptions JsonSerializationOptions { get; } = new JsonSerializerOptions
+ internal static JsonSerializerOptions JsonSerializationOptions { get; } = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
diff --git a/ElevenLabs-DotNet/Voices/VoicesEndpoint.cs b/ElevenLabs-DotNet/Voices/VoicesEndpoint.cs
index cdbeaad..acdd203 100644
--- a/ElevenLabs-DotNet/Voices/VoicesEndpoint.cs
+++ b/ElevenLabs-DotNet/Voices/VoicesEndpoint.cs
@@ -130,7 +130,7 @@ public async Task GetVoiceAsync(string voiceId, bool withSettings = false
///
/// Edit your settings for a specific voice.
///
- /// Id of the voice settings to edit.
+ /// The id of the voice settings to edit.
/// .
/// Optional, .
/// True, if voice settings was successfully edited.
@@ -203,6 +203,101 @@ public async Task AddVoiceAsync(string name, IEnumerable samplePa
return await GetVoiceAsync(voiceResponse.VoiceId, cancellationToken: cancellationToken).ConfigureAwait(false);
}
+ ///
+ /// Add a new voice to your collection of voices in VoiceLab from a stream
+ ///
+ /// Name of the voice you want to add.
+ /// Collection of samples as an array of bytes to be used for the new voice
+ /// Optional, labels for the new voice.
+ /// Optional, .
+ public async Task AddVoiceAsync(string name, IEnumerable samples, IReadOnlyDictionary labels = null, CancellationToken cancellationToken = default)
+ {
+ var form = new MultipartFormDataContent();
+
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ if (samples == null)
+ {
+ throw new ArgumentNullException(nameof(samples));
+ }
+
+ form.Add(new StringContent(name), "name");
+
+ var fileItr = 0;
+
+ foreach (var content in samples)
+ {
+ try
+ {
+ form.Add(new ByteArrayContent(content), "files", $"file-{fileItr++}");
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ }
+ }
+
+ if (labels != null)
+ {
+ form.Add(new StringContent(JsonSerializer.Serialize(labels)), "labels");
+ }
+
+ var response = await client.Client.PostAsync(GetUrl("/add"), form, cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ var voiceResponse = JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions);
+ return await GetVoiceAsync(voiceResponse.VoiceId, cancellationToken: cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Add a new voice to your collection of voices in VoiceLab from a stream
+ ///
+ /// Name of the voice you want to add.
+ /// Collection of samples as a stream to be used for the new voice
+ /// Optional, labels for the new voice.
+ /// Optional, .
+ public async Task AddVoiceAsync(string name, IEnumerable sampleStreams, IReadOnlyDictionary labels = null, CancellationToken cancellationToken = default)
+ {
+ var form = new MultipartFormDataContent();
+
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ if (sampleStreams == null)
+ {
+ throw new ArgumentNullException(nameof(sampleStreams));
+ }
+
+ form.Add(new StringContent(name), "name");
+ var fileItr = 0;
+
+ foreach (var voiceStream in sampleStreams)
+ {
+ try
+ {
+ form.Add(new StreamContent(voiceStream), "files", $"file-{fileItr++}");
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ }
+ }
+
+ if (labels != null)
+ {
+ form.Add(new StringContent(JsonSerializer.Serialize(labels)), "labels");
+ }
+
+ var response = await client.Client.PostAsync(GetUrl("/add"), form, cancellationToken).ConfigureAwait(false);
+ var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
+ var voiceResponse = JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions);
+ return await GetVoiceAsync(voiceResponse.VoiceId, cancellationToken: cancellationToken).ConfigureAwait(false);
+ }
+
///
/// Edit a voice created by you.
///
diff --git a/README.md b/README.md
index f70299e..848f51c 100644
--- a/README.md
+++ b/README.md
@@ -144,7 +144,7 @@ In this example, we demonstrate how to set up and use `ElevenLabsProxyStartup` i
- Powershell install: `Install-Package ElevenLabs-DotNet-Proxy`
- Manually editing .csproj: ``
3. Create a new class that inherits from `AbstractAuthenticationFilter` and override the `ValidateAuthentication` method. This will implement the `IAuthenticationFilter` that you will use to check user session token against your internal server.
-4. In `Program.cs`, create a new proxy web application by calling `ElevenLabsProxyStartup.CreateDefaultHost` method, passing your custom `AuthenticationFilter` as a type argument.
+4. In `Program.cs`, create a new proxy web application by calling `ElevenLabsProxyStartup.CreateWebApplication` method, passing your custom `AuthenticationFilter` as a type argument.
5. Create `ElevenLabsAuthentication` and `ElevenLabsClientSettings` as you would normally with your API keys, org id, or Azure settings.
```csharp
@@ -156,7 +156,19 @@ public partial class Program
{
// You will need to implement your own class to properly test
// custom issued tokens you've setup for your end users.
- if (!request["xi-api-key"].ToString().Contains(userToken))
+ if (!request["xi-api-key"].ToString().Contains(TestUserToken))
+ {
+ throw new AuthenticationException("User is not authorized");
+ }
+ }
+
+ public override async Task ValidateAuthenticationAsync(IHeaderDictionary request)
+ {
+ await Task.CompletedTask; // remote resource call
+
+ // You will need to implement your own class to properly test
+ // custom issued tokens you've setup for your end users.
+ if (!request["xi-api-key"].ToString().Contains(TestUserToken))
{
throw new AuthenticationException("User is not authorized");
}
@@ -165,9 +177,9 @@ public partial class Program
public static void Main(string[] args)
{
- var client = new ElevenLabsClient();
- var proxy = ElevenLabsProxyStartup.CreateDefaultHost(args, client);
- proxy.Run();
+ var auth = ElevenLabsAuthentication.LoadFromEnv();
+ var client = new ElevenLabsClient(auth);
+ ElevenLabsProxyStartup.CreateWebApplication(args, client).Run();
}
}
```