Skip to content

Commit

Permalink
update to .net 8
Browse files Browse the repository at this point in the history
update disposable usages
fix voice adding and editing
  • Loading branch information
StephenHodgson committed Sep 1, 2024
1 parent 06f49fe commit cbd8d68
Show file tree
Hide file tree
Showing 17 changed files with 216 additions and 188 deletions.
2 changes: 1 addition & 1 deletion ElevenLabs-DotNet-Proxy/ElevenLabs-DotNet-Proxy.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<Authors>Stephen Hodgson</Authors>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace ElevenLabs.Proxy
public abstract class AbstractAuthenticationFilter : IAuthenticationFilter
{
/// <inheritdoc />
public abstract void ValidateAuthentication(IHeaderDictionary request);
public virtual void ValidateAuthentication(IHeaderDictionary request) { }

/// <inheritdoc />
public abstract Task ValidateAuthenticationAsync(IHeaderDictionary request);
Expand Down
23 changes: 11 additions & 12 deletions ElevenLabs-DotNet-Proxy/Proxy/EndpointRouteBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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();
}

Expand All @@ -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);
}
Expand All @@ -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);
}

Expand Down
2 changes: 2 additions & 0 deletions ElevenLabs-DotNet-Proxy/Proxy/IAuthenticationFilter.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -17,6 +18,7 @@ public interface IAuthenticationFilter
/// </summary>
/// <param name="request"></param>
/// <exception cref="AuthenticationException"></exception>
[Obsolete("Use Async overload")]
void ValidateAuthentication(IHeaderDictionary request);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>false</ImplicitUsings>
<IsPackable>false</IsPackable>
Expand Down
10 changes: 0 additions & 10 deletions ElevenLabs-DotNet-Tests-Proxy/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion ElevenLabs-DotNet-Tests/ElevenLabs-DotNet-Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>ElevenLabs</RootNamespace>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
Expand Down
2 changes: 1 addition & 1 deletion ElevenLabs-DotNet-Tests/TestFixture_000_Proxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
11 changes: 7 additions & 4 deletions ElevenLabs-DotNet-Tests/TestFixture_03_VoicesEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion ElevenLabs-DotNet/ElevenLabs-DotNet.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Title>ElevenLabs-DotNet</Title>
<Product>ElevenLabs-DotNet</Product>
Expand Down
2 changes: 2 additions & 0 deletions ElevenLabs-DotNet/ElevenLabsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion ElevenLabs-DotNet/Models/ModelsEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public ModelsEndpoint(ElevenLabsClient client) : base(client) { }
/// <returns>A list of <see cref="Model"/>s you can use.</returns>
public async Task<IReadOnlyList<Model>> 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<IReadOnlyList<Model>>(responseAsString, ElevenLabsClient.JsonSerializationOptions);
}
Expand Down
4 changes: 2 additions & 2 deletions ElevenLabs-DotNet/TextToSpeech/TextToSpeechEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public async Task<VoiceClip> 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<string, string>
{
{ OutputFormatParameter, outputFormat.ToString().ToLower() }
Expand All @@ -93,7 +93,7 @@ public async Task<VoiceClip> 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();

Expand Down
4 changes: 2 additions & 2 deletions ElevenLabs-DotNet/Users/UserEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public UserEndpoint(ElevenLabsClient client) : base(client) { }
/// </summary>
public async Task<UserInfo> 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<UserInfo>(responseAsString, ElevenLabsClient.JsonSerializationOptions);
}
Expand All @@ -31,7 +31,7 @@ public async Task<UserInfo> GetUserInfoAsync(CancellationToken cancellationToken
/// </summary>
public async Task<SubscriptionInfo> 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<SubscriptionInfo>(responseAsString, ElevenLabsClient.JsonSerializationOptions);
}
Expand Down
10 changes: 5 additions & 5 deletions ElevenLabs-DotNet/VoiceGeneration/VoiceGenerationEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public VoiceGenerationEndpoint(ElevenLabsClient client) : base(client) { }
/// <returns><see cref="GeneratedVoiceOptions"/>.</returns>
public async Task<GeneratedVoiceOptions> 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<GeneratedVoiceOptions>(responseAsString, ElevenLabsClient.JsonSerializationOptions);
}
Expand All @@ -37,8 +37,8 @@ public async Task<GeneratedVoiceOptions> GetVoiceGenerationOptionsAsync(Cancella
/// <returns>Tuple with generated voice id and audio data.</returns>
public async Task<Tuple<string, ReadOnlyMemory<byte>>> 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);
Expand All @@ -64,8 +64,8 @@ public async Task<Tuple<string, ReadOnlyMemory<byte>>> GenerateVoicePreviewAsync
/// <returns><see cref="Voice"/>.</returns>
public async Task<Voice> 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<Voice>(responseAsString, ElevenLabsClient.JsonSerializationOptions);
}
Expand Down
97 changes: 97 additions & 0 deletions ElevenLabs-DotNet/Voices/VoiceRequest.cs
Original file line number Diff line number Diff line change
@@ -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<string, string> labels = null, string description = null)
: this(name, [samplePath], labels, description)
{
}

public VoiceRequest(string name, IEnumerable<string> samples, IReadOnlyDictionary<string, string> labels = null, string description = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(name);
ArgumentNullException.ThrowIfNull(samples);

Name = name;
Description = description;
Labels = labels;
Samples = samples.ToDictionary<string, string, Stream>(Path.GetFileName, File.OpenRead);
}

public VoiceRequest(string name, byte[] sample, IReadOnlyDictionary<string, string> labels = null, string description = null)
: this(name, [sample], labels, description)
{
}

public VoiceRequest(string name, IEnumerable<byte[]> samples, IReadOnlyDictionary<string, string> labels = null, string description = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(name);
ArgumentNullException.ThrowIfNull(samples);

Name = name;
Description = description;
Labels = labels;
var count = 0;
Samples = samples.ToDictionary<byte[], string, Stream>(_ => $"file-{count++}", sample => new MemoryStream(sample));
}

public VoiceRequest(string name, Stream sample, IReadOnlyDictionary<string, string> labels = null, string description = null)
: this(name, [sample], labels, description)
{
}

public VoiceRequest(string name, IEnumerable<Stream> samples, IReadOnlyDictionary<string, string> 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<string, string> Labels { get; }

public IReadOnlyDictionary<string, Stream> 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);
}
}
}
Loading

0 comments on commit cbd8d68

Please sign in to comment.