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

Fix/voices #54

Merged
merged 2 commits into from
Sep 1, 2024
Merged
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
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/AbstractTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
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: 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
Loading