From cbd8d68bfbfc0f458eca39b18f66754468e07f6f Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 1 Sep 2024 15:10:44 -0400 Subject: [PATCH 1/2] update to .net 8 update disposable usages fix voice adding and editing --- .../ElevenLabs-DotNet-Proxy.csproj | 2 +- .../Proxy/AbstractAuthenticationFilter.cs | 2 +- .../Proxy/EndpointRouteBuilder.cs | 23 +- .../Proxy/IAuthenticationFilter.cs | 2 + .../ElevenLabs-DotNet-Tests-Proxy.csproj | 2 +- ElevenLabs-DotNet-Tests-Proxy/Program.cs | 10 - .../ElevenLabs-DotNet-Tests.csproj | 2 +- .../TestFixture_000_Proxy.cs | 2 +- .../TestFixture_03_VoicesEndpoint.cs | 11 +- ElevenLabs-DotNet/ElevenLabs-DotNet.csproj | 2 +- ElevenLabs-DotNet/ElevenLabsClient.cs | 2 + ElevenLabs-DotNet/Models/ModelsEndpoint.cs | 2 +- .../TextToSpeech/TextToSpeechEndpoint.cs | 4 +- ElevenLabs-DotNet/Users/UserEndpoint.cs | 4 +- .../VoiceGenerationEndpoint.cs | 10 +- ElevenLabs-DotNet/Voices/VoiceRequest.cs | 97 ++++++++ ElevenLabs-DotNet/Voices/VoicesEndpoint.cs | 227 +++++++----------- 17 files changed, 216 insertions(+), 188 deletions(-) create mode 100644 ElevenLabs-DotNet/Voices/VoiceRequest.cs diff --git a/ElevenLabs-DotNet-Proxy/ElevenLabs-DotNet-Proxy.csproj b/ElevenLabs-DotNet-Proxy/ElevenLabs-DotNet-Proxy.csproj index cd8ed77..17cef2f 100644 --- a/ElevenLabs-DotNet-Proxy/ElevenLabs-DotNet-Proxy.csproj +++ b/ElevenLabs-DotNet-Proxy/ElevenLabs-DotNet-Proxy.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 disable disable Stephen Hodgson diff --git a/ElevenLabs-DotNet-Proxy/Proxy/AbstractAuthenticationFilter.cs b/ElevenLabs-DotNet-Proxy/Proxy/AbstractAuthenticationFilter.cs index 37a4f12..824f918 100644 --- a/ElevenLabs-DotNet-Proxy/Proxy/AbstractAuthenticationFilter.cs +++ b/ElevenLabs-DotNet-Proxy/Proxy/AbstractAuthenticationFilter.cs @@ -9,7 +9,7 @@ namespace ElevenLabs.Proxy public abstract class AbstractAuthenticationFilter : IAuthenticationFilter { /// - public abstract void ValidateAuthentication(IHeaderDictionary request); + public virtual void ValidateAuthentication(IHeaderDictionary request) { } /// public abstract Task ValidateAuthenticationAsync(IHeaderDictionary request); diff --git a/ElevenLabs-DotNet-Proxy/Proxy/EndpointRouteBuilder.cs b/ElevenLabs-DotNet-Proxy/Proxy/EndpointRouteBuilder.cs index c3ebfbd..995a02c 100644 --- a/ElevenLabs-DotNet-Proxy/Proxy/EndpointRouteBuilder.cs +++ b/ElevenLabs-DotNet-Proxy/Proxy/EndpointRouteBuilder.cs @@ -57,9 +57,10 @@ async Task HandleRequest(HttpContext httpContext, string endpoint) { try { +#pragma warning disable CS0618 // Type or member is obsolete // ReSharper disable once MethodHasAsyncOverload - // just in case either method is implemented we call it twice. authenticationFilter.ValidateAuthentication(httpContext.Request.Headers); +#pragma warning restore CS0618 // Type or member is obsolete await authenticationFilter.ValidateAuthenticationAsync(httpContext.Request.Headers); var method = new HttpMethod(httpContext.Request.Method); @@ -70,6 +71,11 @@ async Task HandleRequest(HttpContext httpContext, string endpoint) using var request = new HttpRequestMessage(method, uri); request.Content = new StreamContent(httpContext.Request.Body); + if (httpContext.Request.Body.CanSeek) + { + httpContext.Request.Body.Position = 0; + } + if (httpContext.Request.ContentType != null) { request.Content.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse(httpContext.Request.ContentType); @@ -80,21 +86,13 @@ async Task HandleRequest(HttpContext httpContext, string endpoint) 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(); } @@ -103,6 +101,7 @@ async Task HandleRequest(HttpContext httpContext, string endpoint) if (httpContext.Response.ContentType.Equals(streamingContent)) { + using var reader = new StreamReader(await request.Content.ReadAsStreamAsync()); var stream = await proxyResponse.Content.ReadAsStreamAsync(); await WriteServerStreamEventsAsync(httpContext, stream); } @@ -119,7 +118,7 @@ async Task HandleRequest(HttpContext httpContext, string endpoint) catch (Exception e) { httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; - var response = JsonSerializer.Serialize(new { error = new { e.Message, e.StackTrace } }); + var response = JsonSerializer.Serialize(new { error = new { message = e.Message, stackTrace = e.StackTrace } }); await httpContext.Response.WriteAsync(response); } diff --git a/ElevenLabs-DotNet-Proxy/Proxy/IAuthenticationFilter.cs b/ElevenLabs-DotNet-Proxy/Proxy/IAuthenticationFilter.cs index 2b692e8..f3e6e28 100644 --- a/ElevenLabs-DotNet-Proxy/Proxy/IAuthenticationFilter.cs +++ b/ElevenLabs-DotNet-Proxy/Proxy/IAuthenticationFilter.cs @@ -1,6 +1,7 @@ // Licensed under the MIT License. See LICENSE in the project root for license information. using Microsoft.AspNetCore.Http; +using System; using System.Security.Authentication; using System.Threading.Tasks; @@ -17,6 +18,7 @@ public interface IAuthenticationFilter /// /// /// + [Obsolete("Use Async overload")] void ValidateAuthentication(IHeaderDictionary request); /// diff --git a/ElevenLabs-DotNet-Tests-Proxy/ElevenLabs-DotNet-Tests-Proxy.csproj b/ElevenLabs-DotNet-Tests-Proxy/ElevenLabs-DotNet-Tests-Proxy.csproj index c818a32..c523cdf 100644 --- a/ElevenLabs-DotNet-Tests-Proxy/ElevenLabs-DotNet-Tests-Proxy.csproj +++ b/ElevenLabs-DotNet-Tests-Proxy/ElevenLabs-DotNet-Tests-Proxy.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 enable false false diff --git a/ElevenLabs-DotNet-Tests-Proxy/Program.cs b/ElevenLabs-DotNet-Tests-Proxy/Program.cs index 97bfc42..7e77714 100644 --- a/ElevenLabs-DotNet-Tests-Proxy/Program.cs +++ b/ElevenLabs-DotNet-Tests-Proxy/Program.cs @@ -18,16 +18,6 @@ public partial class Program // ReSharper disable once ClassNeverInstantiated.Local private class AuthenticationFilter : AbstractAuthenticationFilter { - public override void ValidateAuthentication(IHeaderDictionary request) - { - // 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 override async Task ValidateAuthenticationAsync(IHeaderDictionary request) { await Task.CompletedTask; // remote resource call diff --git a/ElevenLabs-DotNet-Tests/ElevenLabs-DotNet-Tests.csproj b/ElevenLabs-DotNet-Tests/ElevenLabs-DotNet-Tests.csproj index f849cd1..d96b7fe 100644 --- a/ElevenLabs-DotNet-Tests/ElevenLabs-DotNet-Tests.csproj +++ b/ElevenLabs-DotNet-Tests/ElevenLabs-DotNet-Tests.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 ElevenLabs disable disable diff --git a/ElevenLabs-DotNet-Tests/TestFixture_000_Proxy.cs b/ElevenLabs-DotNet-Tests/TestFixture_000_Proxy.cs index a9590b3..a4108a0 100644 --- a/ElevenLabs-DotNet-Tests/TestFixture_000_Proxy.cs +++ b/ElevenLabs-DotNet-Tests/TestFixture_000_Proxy.cs @@ -13,7 +13,7 @@ internal class TestFixture_000_Proxy : AbstractTestFixture [Test] public async Task Test_01_Health() { - var response = await HttpClient.GetAsync("/health"); + using var response = await HttpClient.GetAsync("/health"); var responseAsString = await response.Content.ReadAsStringAsync(); Console.WriteLine($"[{response.StatusCode}] {responseAsString}"); Assert.IsTrue(HttpStatusCode.OK == response.StatusCode); diff --git a/ElevenLabs-DotNet-Tests/TestFixture_03_VoicesEndpoint.cs b/ElevenLabs-DotNet-Tests/TestFixture_03_VoicesEndpoint.cs index a3cbaf0..1fd9290 100644 --- a/ElevenLabs-DotNet-Tests/TestFixture_03_VoicesEndpoint.cs +++ b/ElevenLabs-DotNet-Tests/TestFixture_03_VoicesEndpoint.cs @@ -93,7 +93,8 @@ public async Task Test_05_AddVoice() { "accent", "american" } }; var clipPath = Path.GetFullPath("../../../Assets/test_sample_01.ogg"); - var result = await ElevenLabsClient.VoicesEndpoint.AddVoiceAsync("Test Voice", new[] { clipPath }, testLabels); + var request = new VoiceRequest("Test Voice", clipPath, testLabels); + var result = await ElevenLabsClient.VoicesEndpoint.AddVoiceAsync(request); Assert.NotNull(result); Console.WriteLine($"{result.Name}"); Assert.IsNotEmpty(result.Samples); @@ -109,7 +110,8 @@ public async Task Test_06_AddVoiceFromByteArray() }; var clipPath = Path.GetFullPath("../../../Assets/test_sample_01.ogg"); var clipData = await File.ReadAllBytesAsync(clipPath); - var result = await ElevenLabsClient.VoicesEndpoint.AddVoiceAsync("Test Voice", new[] { clipData }, testLabels); + var request = new VoiceRequest("Test Voice", clipData, testLabels); + var result = await ElevenLabsClient.VoicesEndpoint.AddVoiceAsync(request); Assert.NotNull(result); Console.WriteLine($"{result.Name}"); Assert.IsNotEmpty(result.Samples); @@ -127,7 +129,8 @@ public async Task Test_07_AddVoiceFromStream() var clipPath = Path.GetFullPath("../../../Assets/test_sample_01.ogg"); await using var fs = File.OpenRead(clipPath); - var result = await ElevenLabsClient.VoicesEndpoint.AddVoiceAsync("Test Voice", new[] { fs }, testLabels); + var request = new VoiceRequest("Test Voice", fs, testLabels); + var result = await ElevenLabsClient.VoicesEndpoint.AddVoiceAsync(request); Assert.NotNull(result); Console.WriteLine($"{result.Name}"); Assert.IsNotEmpty(result.Samples); @@ -148,7 +151,7 @@ public async Task Test_08_EditVoice() { "key", "value" } }; var clipPath = Path.GetFullPath("../../../Assets/test_sample_01.ogg"); - var result = await ElevenLabsClient.VoicesEndpoint.EditVoiceAsync(voiceToEdit, new[] { clipPath }, testLabels); + var result = await ElevenLabsClient.VoicesEndpoint.EditVoiceAsync(voiceToEdit, [clipPath], testLabels); Assert.NotNull(result); Assert.IsTrue(result); } diff --git a/ElevenLabs-DotNet/ElevenLabs-DotNet.csproj b/ElevenLabs-DotNet/ElevenLabs-DotNet.csproj index 1168dc2..4ab9a69 100644 --- a/ElevenLabs-DotNet/ElevenLabs-DotNet.csproj +++ b/ElevenLabs-DotNet/ElevenLabs-DotNet.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 latest ElevenLabs-DotNet ElevenLabs-DotNet diff --git a/ElevenLabs-DotNet/ElevenLabsClient.cs b/ElevenLabs-DotNet/ElevenLabsClient.cs index 09113bc..540abce 100644 --- a/ElevenLabs-DotNet/ElevenLabsClient.cs +++ b/ElevenLabs-DotNet/ElevenLabsClient.cs @@ -8,7 +8,9 @@ using ElevenLabs.VoiceGeneration; using ElevenLabs.Voices; using System; +using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Security.Authentication; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/ElevenLabs-DotNet/Models/ModelsEndpoint.cs b/ElevenLabs-DotNet/Models/ModelsEndpoint.cs index 0e91727..78f2c5d 100644 --- a/ElevenLabs-DotNet/Models/ModelsEndpoint.cs +++ b/ElevenLabs-DotNet/Models/ModelsEndpoint.cs @@ -20,7 +20,7 @@ public ModelsEndpoint(ElevenLabsClient client) : base(client) { } /// A list of s you can use. public async Task> GetModelsAsync(CancellationToken cancellationToken = default) { - var response = await client.Client.GetAsync(GetUrl(), cancellationToken).ConfigureAwait(false); + using var response = await client.Client.GetAsync(GetUrl(), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize>(responseAsString, ElevenLabsClient.JsonSerializationOptions); } diff --git a/ElevenLabs-DotNet/TextToSpeech/TextToSpeechEndpoint.cs b/ElevenLabs-DotNet/TextToSpeech/TextToSpeechEndpoint.cs index ad2a9af..e036fa1 100644 --- a/ElevenLabs-DotNet/TextToSpeech/TextToSpeechEndpoint.cs +++ b/ElevenLabs-DotNet/TextToSpeech/TextToSpeechEndpoint.cs @@ -77,7 +77,7 @@ public async Task TextToSpeechAsync(string text, Voice voice, VoiceSe } var defaultVoiceSettings = voiceSettings ?? voice.Settings ?? await client.VoicesEndpoint.GetDefaultVoiceSettingsAsync(cancellationToken); - var payload = JsonSerializer.Serialize(new TextToSpeechRequest(text, model, defaultVoiceSettings)).ToJsonStringContent(); + using var payload = JsonSerializer.Serialize(new TextToSpeechRequest(text, model, defaultVoiceSettings)).ToJsonStringContent(); var parameters = new Dictionary { { OutputFormatParameter, outputFormat.ToString().ToLower() } @@ -93,7 +93,7 @@ public async Task TextToSpeechAsync(string text, Voice voice, VoiceSe var requestOption = partialClipCallback == null ? HttpCompletionOption.ResponseContentRead : HttpCompletionOption.ResponseHeadersRead; - var response = await client.Client.SendAsync(postRequest, requestOption, cancellationToken); + using var response = await client.Client.SendAsync(postRequest, requestOption, cancellationToken); await response.CheckResponseAsync(EnableDebug, payload, cancellationToken).ConfigureAwait(false); var clipId = response.Headers.GetValues(HistoryItemId).FirstOrDefault(); diff --git a/ElevenLabs-DotNet/Users/UserEndpoint.cs b/ElevenLabs-DotNet/Users/UserEndpoint.cs index 59dc0b5..a019602 100644 --- a/ElevenLabs-DotNet/Users/UserEndpoint.cs +++ b/ElevenLabs-DotNet/Users/UserEndpoint.cs @@ -21,7 +21,7 @@ public UserEndpoint(ElevenLabsClient client) : base(client) { } /// public async Task GetUserInfoAsync(CancellationToken cancellationToken = default) { - var response = await client.Client.GetAsync(GetUrl(), cancellationToken).ConfigureAwait(false); + using var response = await client.Client.GetAsync(GetUrl(), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions); } @@ -31,7 +31,7 @@ public async Task GetUserInfoAsync(CancellationToken cancellationToken /// public async Task GetSubscriptionInfoAsync(CancellationToken cancellationToken = default) { - var response = await client.Client.GetAsync(GetUrl("/subscription"), cancellationToken).ConfigureAwait(false); + using var response = await client.Client.GetAsync(GetUrl("/subscription"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions); } diff --git a/ElevenLabs-DotNet/VoiceGeneration/VoiceGenerationEndpoint.cs b/ElevenLabs-DotNet/VoiceGeneration/VoiceGenerationEndpoint.cs index cb191df..1ad1fc6 100644 --- a/ElevenLabs-DotNet/VoiceGeneration/VoiceGenerationEndpoint.cs +++ b/ElevenLabs-DotNet/VoiceGeneration/VoiceGenerationEndpoint.cs @@ -24,7 +24,7 @@ public VoiceGenerationEndpoint(ElevenLabsClient client) : base(client) { } /// . public async Task GetVoiceGenerationOptionsAsync(CancellationToken cancellationToken = default) { - var response = await client.Client.GetAsync(GetUrl("/generate-voice/parameters"), cancellationToken).ConfigureAwait(false); + using var response = await client.Client.GetAsync(GetUrl("/generate-voice/parameters"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken: cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions); } @@ -37,8 +37,8 @@ public async Task GetVoiceGenerationOptionsAsync(Cancella /// Tuple with generated voice id and audio data. public async Task>> GenerateVoicePreviewAsync(GeneratedVoicePreviewRequest generatedVoicePreviewRequest, CancellationToken cancellationToken = default) { - var payload = JsonSerializer.Serialize(generatedVoicePreviewRequest, ElevenLabsClient.JsonSerializationOptions).ToJsonStringContent(); - var response = await client.Client.PostAsync(GetUrl("/generate-voice"), payload, cancellationToken).ConfigureAwait(false); + using var payload = JsonSerializer.Serialize(generatedVoicePreviewRequest, ElevenLabsClient.JsonSerializationOptions).ToJsonStringContent(); + using var response = await client.Client.PostAsync(GetUrl("/generate-voice"), payload, cancellationToken).ConfigureAwait(false); await response.CheckResponseAsync(EnableDebug, payload, cancellationToken).ConfigureAwait(false); var generatedVoiceId = response.Headers.FirstOrDefault(pair => pair.Key == "generated_voice_id").Value.FirstOrDefault(); await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); @@ -64,8 +64,8 @@ public async Task>> GenerateVoicePreviewAsync /// . public async Task CreateVoiceAsync(CreateVoiceRequest createVoiceRequest, CancellationToken cancellationToken = default) { - var payload = JsonSerializer.Serialize(createVoiceRequest).ToJsonStringContent(); - var response = await client.Client.PostAsync(GetUrl("/create-voice"), payload, cancellationToken).ConfigureAwait(false); + using var payload = JsonSerializer.Serialize(createVoiceRequest).ToJsonStringContent(); + using var response = await client.Client.PostAsync(GetUrl("/create-voice"), payload, cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken: cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions); } diff --git a/ElevenLabs-DotNet/Voices/VoiceRequest.cs b/ElevenLabs-DotNet/Voices/VoiceRequest.cs new file mode 100644 index 0000000..b22fa32 --- /dev/null +++ b/ElevenLabs-DotNet/Voices/VoiceRequest.cs @@ -0,0 +1,97 @@ +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace ElevenLabs.Voices +{ + public sealed class VoiceRequest : IDisposable + { + public VoiceRequest(string name, string samplePath, IReadOnlyDictionary labels = null, string description = null) + : this(name, [samplePath], labels, description) + { + } + + public VoiceRequest(string name, IEnumerable samples, IReadOnlyDictionary labels = null, string description = null) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + ArgumentNullException.ThrowIfNull(samples); + + Name = name; + Description = description; + Labels = labels; + Samples = samples.ToDictionary(Path.GetFileName, File.OpenRead); + } + + public VoiceRequest(string name, byte[] sample, IReadOnlyDictionary labels = null, string description = null) + : this(name, [sample], labels, description) + { + } + + public VoiceRequest(string name, IEnumerable samples, IReadOnlyDictionary labels = null, string description = null) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + ArgumentNullException.ThrowIfNull(samples); + + Name = name; + Description = description; + Labels = labels; + var count = 0; + Samples = samples.ToDictionary(_ => $"file-{count++}", sample => new MemoryStream(sample)); + } + + public VoiceRequest(string name, Stream sample, IReadOnlyDictionary labels = null, string description = null) + : this(name, [sample], labels, description) + { + } + + public VoiceRequest(string name, IEnumerable samples, IReadOnlyDictionary labels = null, string description = null) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + ArgumentNullException.ThrowIfNull(samples); + + Name = name; + Description = description; + Labels = labels; + var count = 0; + Samples = samples.ToDictionary(_ => $"file-{count++}", sample => sample); + } + + ~VoiceRequest() => Dispose(false); + + public string Name { get; } + + public string Description { get; } + + public IReadOnlyDictionary Labels { get; } + + public IReadOnlyDictionary Samples { get; } + + private void Dispose(bool disposing) + { + if (disposing) + { + foreach (var (_, sample) in Samples) + { + try + { + sample?.Close(); + sample?.Dispose(); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/ElevenLabs-DotNet/Voices/VoicesEndpoint.cs b/ElevenLabs-DotNet/Voices/VoicesEndpoint.cs index 3b7e61f..0246f56 100644 --- a/ElevenLabs-DotNet/Voices/VoicesEndpoint.cs +++ b/ElevenLabs-DotNet/Voices/VoicesEndpoint.cs @@ -5,7 +5,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; @@ -52,7 +54,7 @@ public async Task> GetAllVoicesAsync(CancellationToken canc /// of s. public async Task> GetAllVoicesAsync(bool downloadSettings, CancellationToken cancellationToken = default) { - var response = await client.Client.GetAsync(GetUrl(), cancellationToken).ConfigureAwait(false); + using var response = await client.Client.GetAsync(GetUrl(), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); var voices = JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions).Voices; @@ -83,7 +85,7 @@ async Task LocalGetVoiceSettingsAsync() /// . public async Task GetDefaultVoiceSettingsAsync(CancellationToken cancellationToken = default) { - var response = await client.Client.GetAsync(GetUrl("/settings/default"), cancellationToken).ConfigureAwait(false); + using var response = await client.Client.GetAsync(GetUrl("/settings/default"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions); } @@ -101,7 +103,7 @@ public async Task GetVoiceSettingsAsync(string voiceId, Cancellat throw new ArgumentNullException(nameof(voiceId)); } - var response = await client.Client.GetAsync(GetUrl($"/{voiceId}/settings"), cancellationToken).ConfigureAwait(false); + using var response = await client.Client.GetAsync(GetUrl($"/{voiceId}/settings"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions); } @@ -120,7 +122,7 @@ public async Task GetVoiceAsync(string voiceId, bool withSettings = false throw new ArgumentNullException(nameof(voiceId)); } - var response = await client.Client.GetAsync(GetUrl($"/{voiceId}?with_settings={withSettings}"), cancellationToken).ConfigureAwait(false); + using var response = await client.Client.GetAsync(GetUrl($"/{voiceId}?with_settings={withSettings.ToString().ToLower()}"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions); } @@ -139,8 +141,8 @@ public async Task EditVoiceSettingsAsync(string voiceId, VoiceSettings voi throw new ArgumentNullException(nameof(voiceId)); } - var payload = JsonSerializer.Serialize(voiceSettings).ToJsonStringContent(); - var response = await client.Client.PostAsync(GetUrl($"/{voiceId}/settings/edit"), payload, cancellationToken).ConfigureAwait(false); + using var payload = JsonSerializer.Serialize(voiceSettings).ToJsonStringContent(); + using var response = await client.Client.PostAsync(GetUrl($"/{voiceId}/settings/edit"), payload, cancellationToken).ConfigureAwait(false); await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return response.IsSuccessStatusCode; } @@ -152,54 +154,9 @@ public async Task EditVoiceSettingsAsync(string voiceId, VoiceSettings voi /// Collection of file paths to use as samples for the new voice. /// Optional, labels for the new voice. /// Optional, . - public async Task AddVoiceAsync(string name, IEnumerable samplePaths = null, IReadOnlyDictionary labels = null, CancellationToken cancellationToken = default) - { - var form = new MultipartFormDataContent(); - - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - form.Add(new StringContent(name), "name"); - - if (samplePaths != null) - { - var paths = samplePaths.Where(path => !string.IsNullOrWhiteSpace(path)).ToList(); - - if (paths.Any()) - { - foreach (var sample in paths) - { - if (!File.Exists(sample)) - { - Console.WriteLine($"No sample clip found at {sample}!"); - continue; - } - - try - { - var fileBytes = await File.ReadAllBytesAsync(sample, cancellationToken); - form.Add(new ByteArrayContent(fileBytes), "files", Path.GetFileName(sample)); - } - 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); - } + [Obsolete("Use new overload with VoiceRequest.")] + public async Task AddVoiceAsync(string name, IEnumerable samplePaths, IReadOnlyDictionary labels = null, CancellationToken cancellationToken = default) + => await AddVoiceAsync(new VoiceRequest(name, samplePaths, labels), cancellationToken).ConfigureAwait(false); /// /// Add a new voice to your collection of voices in VoiceLab from a stream @@ -208,46 +165,9 @@ public async Task AddVoiceAsync(string name, IEnumerable samplePa /// Collection of samples as an array of bytes to be used for the new voice /// Optional, labels for the new voice. /// Optional, . + [Obsolete("Use new overload with VoiceRequest.")] 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); - } + => await AddVoiceAsync(new VoiceRequest(name, samples, labels), cancellationToken).ConfigureAwait(false); /// /// Add a new voice to your collection of voices in VoiceLab from a stream @@ -256,42 +176,55 @@ public async Task AddVoiceAsync(string name, IEnumerable samples, /// Collection of samples as a stream to be used for the new voice /// Optional, labels for the new voice. /// Optional, . + [Obsolete("Use new overload with VoiceRequest.")] public async Task AddVoiceAsync(string name, IEnumerable sampleStreams, IReadOnlyDictionary labels = null, CancellationToken cancellationToken = default) + => await AddVoiceAsync(new VoiceRequest(name, sampleStreams, labels), cancellationToken).ConfigureAwait(false); + + /// + /// Add a new voice to your collection of voices in VoiceLab. + /// + /// . + /// Optional, . + /// . + public async Task AddVoiceAsync(VoiceRequest request, CancellationToken cancellationToken = default) { - var form = new MultipartFormDataContent(); + using var payload = new MultipartFormDataContent(); - if (string.IsNullOrWhiteSpace(name)) + try { - throw new ArgumentNullException(nameof(name)); - } + payload.Add(new StringContent(request.Name), "name"); - if (sampleStreams == null) - { - throw new ArgumentNullException(nameof(sampleStreams)); - } - - form.Add(new StringContent(name), "name"); - var fileItr = 0; - - foreach (var voiceStream in sampleStreams) - { - try + foreach (var (fileName, sample) in request.Samples) { - form.Add(new StreamContent(voiceStream), "files", $"file-{fileItr++}"); + using var audioData = new MemoryStream(); + await sample.CopyToAsync(audioData, cancellationToken).ConfigureAwait(false); + var content = new ByteArrayContent(audioData.ToArray()); + content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + content.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") + { + Name = "files", + FileName = fileName + }; + payload.Add(content, "files", fileName); } - catch (Exception e) + + if (request.Labels != null) { - Console.WriteLine(e); + payload.Add(new StringContent(JsonSerializer.Serialize(request.Labels)), "labels"); } } - - if (labels != null) + finally { - form.Add(new StringContent(JsonSerializer.Serialize(labels)), "labels"); + request.Dispose(); } - var response = await client.Client.PostAsync(GetUrl("/add"), form, cancellationToken).ConfigureAwait(false); - var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); + using var httpRequest = new HttpRequestMessage(HttpMethod.Post, GetUrl("/add")); + httpRequest.Content = payload; + httpRequest.Version = HttpVersion.Version10; + httpRequest.Headers.ExpectContinue = true; + httpRequest.Headers.ConnectionClose = false; + using var response = await client.Client.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var responseAsString = await response.ReadAsStringAsync(EnableDebug, payload, cancellationToken).ConfigureAwait(false); var voiceResponse = JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions); return await GetVoiceAsync(voiceResponse.VoiceId, cancellationToken: cancellationToken).ConfigureAwait(false); } @@ -306,49 +239,51 @@ public async Task AddVoiceAsync(string name, IEnumerable sampleSt /// True, if voice was successfully edited. public async Task EditVoiceAsync(Voice voice, IEnumerable samplePaths = null, IReadOnlyDictionary labels = null, CancellationToken cancellationToken = default) { - var form = new MultipartFormDataContent(); + using var payload = new MultipartFormDataContent(); - if (voice == null) - { - throw new ArgumentNullException(nameof(voice)); - } + ArgumentNullException.ThrowIfNull(voice, nameof(voice)); + ArgumentNullException.ThrowIfNull(samplePaths, nameof(samplePaths)); - form.Add(new StringContent(voice.Name), "name"); + payload.Add(new StringContent(voice.Name), "name"); - if (samplePaths != null) - { - var paths = samplePaths.Where(path => !string.IsNullOrWhiteSpace(path)).ToList(); + var paths = samplePaths.Where(path => !string.IsNullOrWhiteSpace(path)).ToList(); - if (paths.Any()) + if (paths.Any()) + { + foreach (var sample in paths) { - foreach (var sample in paths) + if (!File.Exists(sample)) { - if (!File.Exists(sample)) - { - Console.WriteLine($"No sample clip found at {sample}!"); - continue; - } + Console.WriteLine($"No sample clip found at {sample}!"); + continue; + } - try - { - var fileBytes = await File.ReadAllBytesAsync(sample, cancellationToken); - form.Add(new ByteArrayContent(fileBytes), "files", Path.GetFileName(sample)); - } - catch (Exception e) + try + { + var fileBytes = await File.ReadAllBytesAsync(sample, cancellationToken); + var content = new ByteArrayContent(fileBytes); + content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + content.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { - Console.WriteLine(e); - } + Name = "files", + FileName = Path.GetFileName(sample) + }; + payload.Add(content, "files", Path.GetFileName(sample)); + } + catch (Exception e) + { + Console.WriteLine(e); } } } if (labels != null) { - form.Add(new StringContent(JsonSerializer.Serialize(labels)), "labels"); + payload.Add(new StringContent(JsonSerializer.Serialize(labels)), "labels"); } - var response = await client.Client.PostAsync(GetUrl($"/{voice.Id}/edit"), form, cancellationToken).ConfigureAwait(false); - await response.CheckResponseAsync(EnableDebug, form, cancellationToken).ConfigureAwait(false); + using var response = await client.Client.PostAsync(GetUrl($"/{voice.Id}/edit"), payload, cancellationToken).ConfigureAwait(false); + await response.CheckResponseAsync(EnableDebug, payload, cancellationToken).ConfigureAwait(false); return response.IsSuccessStatusCode; } @@ -365,7 +300,7 @@ public async Task DeleteVoiceAsync(string voiceId, CancellationToken cance throw new ArgumentNullException(nameof(voiceId)); } - var response = await client.Client.DeleteAsync(GetUrl($"/{voiceId}"), cancellationToken).ConfigureAwait(false); + using var response = await client.Client.DeleteAsync(GetUrl($"/{voiceId}"), cancellationToken).ConfigureAwait(false); await response.CheckResponseAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return response.IsSuccessStatusCode; } @@ -393,7 +328,7 @@ public async Task DownloadVoiceSampleAudioAsync(Voice voice, Sample s throw new ArgumentNullException(nameof(sample)); } - var response = await client.Client.GetAsync(GetUrl($"/{voice.Id}/samples/{sample.Id}/audio"), cancellationToken).ConfigureAwait(false); + using var response = await client.Client.GetAsync(GetUrl($"/{voice.Id}/samples/{sample.Id}/audio"), cancellationToken).ConfigureAwait(false); await response.CheckResponseAsync(EnableDebug, cancellationToken).ConfigureAwait(false); await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await using var memoryStream = new MemoryStream(); @@ -420,7 +355,7 @@ public async Task DeleteVoiceSampleAsync(string voiceId, string sampleId, throw new ArgumentNullException(nameof(sampleId)); } - var response = await client.Client.DeleteAsync(GetUrl($"/{voiceId}/samples/{sampleId}"), cancellationToken).ConfigureAwait(false); + using var response = await client.Client.DeleteAsync(GetUrl($"/{voiceId}/samples/{sampleId}"), cancellationToken).ConfigureAwait(false); await response.CheckResponseAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return response.IsSuccessStatusCode; } From 5d1c2f40705f1be2123cdfdbfb52495f88273e45 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 1 Sep 2024 15:26:06 -0400 Subject: [PATCH 2/2] . --- ElevenLabs-DotNet-Tests/AbstractTestFixture.cs | 2 +- ElevenLabs-DotNet/ElevenLabsClient.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/ElevenLabs-DotNet-Tests/AbstractTestFixture.cs b/ElevenLabs-DotNet-Tests/AbstractTestFixture.cs index 61b7217..01a7810 100644 --- a/ElevenLabs-DotNet-Tests/AbstractTestFixture.cs +++ b/ElevenLabs-DotNet-Tests/AbstractTestFixture.cs @@ -31,7 +31,7 @@ protected AbstractTestFixture() var settings = new ElevenLabsClientSettings(domain: domain); var auth = new ElevenLabsAuthentication(TestUserToken); ElevenLabsClient = new ElevenLabsClient(auth, settings, HttpClient); - ElevenLabsClient.EnableDebug = true; + // ElevenLabsClient.EnableDebug = true; } } } \ No newline at end of file diff --git a/ElevenLabs-DotNet/ElevenLabsClient.cs b/ElevenLabs-DotNet/ElevenLabsClient.cs index 540abce..09113bc 100644 --- a/ElevenLabs-DotNet/ElevenLabsClient.cs +++ b/ElevenLabs-DotNet/ElevenLabsClient.cs @@ -8,9 +8,7 @@ using ElevenLabs.VoiceGeneration; using ElevenLabs.Voices; using System; -using System.Net; using System.Net.Http; -using System.Net.Http.Headers; using System.Security.Authentication; using System.Text.Json; using System.Text.Json.Serialization;