From da3200e14786fd6fb56152bfd22b69002379cea9 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Wed, 29 Nov 2023 18:34:44 -0500 Subject: [PATCH] ElevenLabs-DotNet 2.1.0 (#31) - Added ElevenLabsClient.EnableDebug option to enable and disable for all endpoints - Synced changes from unity package - Updated unit test - Updated docs --- .github/workflows/Publish-Nuget.yml | 39 +++++++++++++++---- .../ElevenLabs-DotNet-Tests.csproj | 1 + .../Test_Fixture_02_VoicesEndpoint.cs | 6 +-- .../Test_Fixture_03_TextToSpeechEndpoint.cs | 5 +-- .../Test_Fixture_04_HistoryEndpoint.cs | 10 ++--- .../Test_Fixture_05_VoiceGeneration.cs | 1 + .../ElevenLabsAuthentication.cs | 21 +++++----- .../ElevenLabsClientSettings.cs | 0 .../ElevenLabsBaseEndPoint.cs} | 17 +++++--- .../{ => Common}/OutputFormat.cs | 0 ElevenLabs-DotNet/{ => Common}/VoiceClip.cs | 0 ElevenLabs-DotNet/ElevenLabs-DotNet.csproj | 8 +++- ElevenLabs-DotNet/ElevenLabsClient.cs | 14 +++++-- ElevenLabs-DotNet/History/HistoryEndpoint.cs | 21 +++++----- ElevenLabs-DotNet/Models/ModelsEndpoint.cs | 6 +-- .../TextToSpeech/TextToSpeechEndpoint.cs | 8 ++-- ElevenLabs-DotNet/Users/UserEndpoint.cs | 9 ++--- .../VoiceGenerationEndpoint.cs | 10 ++--- ElevenLabs-DotNet/Voices/VoiceSettings.cs | 6 ++- ElevenLabs-DotNet/Voices/VoicesEndpoint.cs | 28 ++++++------- README.md | 33 +++++++++------- 21 files changed, 151 insertions(+), 92 deletions(-) rename ElevenLabs-DotNet/{ => Authentication}/ElevenLabsAuthentication.cs (92%) rename ElevenLabs-DotNet/{ => Authentication}/ElevenLabsClientSettings.cs (100%) rename ElevenLabs-DotNet/{BaseEndPoint.cs => Common/ElevenLabsBaseEndPoint.cs} (69%) rename ElevenLabs-DotNet/{ => Common}/OutputFormat.cs (100%) rename ElevenLabs-DotNet/{ => Common}/VoiceClip.cs (100%) diff --git a/.github/workflows/Publish-Nuget.yml b/.github/workflows/Publish-Nuget.yml index ba099d1..636806e 100644 --- a/.github/workflows/Publish-Nuget.yml +++ b/.github/workflows/Publish-Nuget.yml @@ -31,31 +31,54 @@ env: jobs: build: if: ${{ !github.event_name == 'pull_request' || !github.event.pull_request.draft }} - runs-on: windows-latest + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 + - uses: actions/setup-dotnet@v3 with: dotnet-version: ${{ env.DOTNET_VERSION }} - - uses: microsoft/setup-msbuild@v1 + + - run: dotnet restore + + - run: dotnet build --configuration Release --no-restore - name: Test Packages - run: dotnet test --configuration Release if: ${{ github.ref != 'refs/heads/main' && github.event_name != 'push' }} + run: dotnet test --configuration Release --collect:"XPlat Code Coverage" --logger:trx --no-build --no-restore --results-directory ./test-results env: ELEVEN_LABS_API_KEY: ${{ secrets.ELEVEN_LABS_API_KEY }} - - name: Build Pack and Publish NuGet Package + - name: Publish Test Results + if: ${{ github.ref != 'refs/heads/main' && github.event_name != 'push' && always() }} + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + files: test-results/**/*.trx + comment_mode: off + report_individual_runs: true + compare_to_earlier_commit: false + + - name: Code Coverage Summary Report + if: ${{ github.ref != 'refs/heads/main' && github.event_name != 'push' && always() }} + uses: irongut/CodeCoverageSummary@v1.3.0 + with: + filename: test-results/**/coverage.cobertura.xml + badge: true + format: 'markdown' + output: 'both' + + - name: Write Coverage Job Summary + if: ${{ github.ref != 'refs/heads/main' && github.event_name != 'push' && always() }} + run: cat code-coverage-results.md >> $GITHUB_STEP_SUMMARY + + - name: Pack and Publish NuGet Package run: | $projectPath = "${{ github.workspace }}\ElevenLabs-DotNet" $proxyProjectPath = "${{ github.workspace }}\ElevenLabs-DotNet-Proxy" - # build all - dotnet build --configuration Release - # pack ElevenLabs-DotNet dotnet pack $projectPath --configuration Release --include-symbols $out = "$projectPath\bin\Release" diff --git a/ElevenLabs-DotNet-Tests/ElevenLabs-DotNet-Tests.csproj b/ElevenLabs-DotNet-Tests/ElevenLabs-DotNet-Tests.csproj index 870b1e7..f849cd1 100644 --- a/ElevenLabs-DotNet-Tests/ElevenLabs-DotNet-Tests.csproj +++ b/ElevenLabs-DotNet-Tests/ElevenLabs-DotNet-Tests.csproj @@ -12,6 +12,7 @@ + diff --git a/ElevenLabs-DotNet-Tests/Test_Fixture_02_VoicesEndpoint.cs b/ElevenLabs-DotNet-Tests/Test_Fixture_02_VoicesEndpoint.cs index dddf173..af1d3be 100644 --- a/ElevenLabs-DotNet-Tests/Test_Fixture_02_VoicesEndpoint.cs +++ b/ElevenLabs-DotNet-Tests/Test_Fixture_02_VoicesEndpoint.cs @@ -43,7 +43,7 @@ public async Task Test_03_GetVoice() var results = await ElevenLabsClient.VoicesEndpoint.GetAllVoicesAsync(); Assert.NotNull(results); Assert.IsNotEmpty(results); - var voiceToGet = results.MinBy(voice => voice.Name); + var voiceToGet = results.OrderBy(voice => voice.Name).FirstOrDefault(); var result = await ElevenLabsClient.VoicesEndpoint.GetVoiceAsync(voiceToGet); Assert.NotNull(result); Console.WriteLine($"{result.Id} | {result.Name} | {result.PreviewUrl}"); @@ -78,7 +78,7 @@ public async Task Test_05_AddVoice() { { "accent", "american" } }; - var clipPath = Path.GetFullPath("..\\..\\..\\Assets\\test_sample_01.ogg"); + var clipPath = Path.GetFullPath("../../../Assets/test_sample_01.ogg"); var result = await ElevenLabsClient.VoicesEndpoint.AddVoiceAsync("Test Voice", new[] { clipPath }, testLabels); Assert.NotNull(result); Console.WriteLine($"{result.Name}"); @@ -99,7 +99,7 @@ public async Task Test_06_EditVoice() { "accent", "american" }, { "key", "value" } }; - var clipPath = Path.GetFullPath("..\\..\\..\\Assets\\test_sample_01.ogg"); + var clipPath = Path.GetFullPath("../../../Assets/test_sample_01.ogg"); var result = await ElevenLabsClient.VoicesEndpoint.EditVoiceAsync(voiceToEdit, new[] { clipPath }, testLabels); Assert.NotNull(result); Assert.IsTrue(result); diff --git a/ElevenLabs-DotNet-Tests/Test_Fixture_03_TextToSpeechEndpoint.cs b/ElevenLabs-DotNet-Tests/Test_Fixture_03_TextToSpeechEndpoint.cs index 83d8ca4..dbd31d8 100644 --- a/ElevenLabs-DotNet-Tests/Test_Fixture_03_TextToSpeechEndpoint.cs +++ b/ElevenLabs-DotNet-Tests/Test_Fixture_03_TextToSpeechEndpoint.cs @@ -14,7 +14,7 @@ internal class Test_Fixture_03_TextToSpeechEndpoint : AbstractTestFixture public async Task Test_01_TextToSpeech() { Assert.NotNull(ElevenLabsClient.TextToSpeechEndpoint); - var voice = (await ElevenLabsClient.VoicesEndpoint.GetAllVoicesAsync()).FirstOrDefault(); + var voice = Voices.Voice.Adam; Assert.NotNull(voice); var defaultVoiceSettings = await ElevenLabsClient.VoicesEndpoint.GetDefaultVoiceSettingsAsync(); var voiceClip = await ElevenLabsClient.TextToSpeechEndpoint.TextToSpeechAsync("The quick brown fox jumps over the lazy dog.", voice, defaultVoiceSettings); @@ -28,10 +28,9 @@ public async Task Test_02_StreamTextToSpeech() Assert.NotNull(ElevenLabsClient.TextToSpeechEndpoint); var voice = (await ElevenLabsClient.VoicesEndpoint.GetAllVoicesAsync()).FirstOrDefault(); Assert.NotNull(voice); - var partialClips = new Queue(); var defaultVoiceSettings = await ElevenLabsClient.VoicesEndpoint.GetDefaultVoiceSettingsAsync(); + var partialClips = new Queue(); var voiceClip = await ElevenLabsClient.TextToSpeechEndpoint.TextToSpeechAsync("The quick brown fox jumps over the lazy dog.", voice, defaultVoiceSettings, - partialClipCallback: async partialClip => { Assert.IsNotNull(partialClip); diff --git a/ElevenLabs-DotNet-Tests/Test_Fixture_04_HistoryEndpoint.cs b/ElevenLabs-DotNet-Tests/Test_Fixture_04_HistoryEndpoint.cs index 34de1e9..8356833 100644 --- a/ElevenLabs-DotNet-Tests/Test_Fixture_04_HistoryEndpoint.cs +++ b/ElevenLabs-DotNet-Tests/Test_Fixture_04_HistoryEndpoint.cs @@ -14,11 +14,11 @@ internal class Test_Fixture_04_HistoryEndpoint : AbstractTestFixture public async Task Test_01_GetHistory() { Assert.NotNull(ElevenLabsClient.HistoryEndpoint); - var results = await ElevenLabsClient.HistoryEndpoint.GetHistoryAsync(); - Assert.NotNull(results); - Assert.IsNotEmpty(results.HistoryItems); + var historyInfo = await ElevenLabsClient.HistoryEndpoint.GetHistoryAsync(); + Assert.NotNull(historyInfo); + Assert.IsNotEmpty(historyInfo.HistoryItems); - foreach (var item in results.HistoryItems.OrderBy(item => item.Date)) + foreach (var item in historyInfo.HistoryItems.OrderBy(item => item.Date)) { Console.WriteLine($"{item.State} {item.Date} | {item.Id} | {item.Text.Length} | {item.Text}"); } @@ -31,7 +31,7 @@ public async Task Test_02_GetHistoryAudio() var historyInfo = await ElevenLabsClient.HistoryEndpoint.GetHistoryAsync(); Assert.NotNull(historyInfo); Assert.IsNotEmpty(historyInfo.HistoryItems); - var downloadItem = historyInfo.HistoryItems.MaxBy(item => item.Date); + var downloadItem = historyInfo.HistoryItems.OrderByDescending(item => item.Date).FirstOrDefault(); Assert.NotNull(downloadItem); Console.WriteLine($"Downloading {downloadItem!.Id}..."); var voiceClip = await ElevenLabsClient.HistoryEndpoint.DownloadHistoryAudioAsync(downloadItem); diff --git a/ElevenLabs-DotNet-Tests/Test_Fixture_05_VoiceGeneration.cs b/ElevenLabs-DotNet-Tests/Test_Fixture_05_VoiceGeneration.cs index a0dd085..dd67898 100644 --- a/ElevenLabs-DotNet-Tests/Test_Fixture_05_VoiceGeneration.cs +++ b/ElevenLabs-DotNet-Tests/Test_Fixture_05_VoiceGeneration.cs @@ -35,6 +35,7 @@ public async Task Test_02_GenerateVoice() Assert.NotNull(result); Console.WriteLine(result.Id); var deleteResult = await ElevenLabsClient.VoicesEndpoint.DeleteVoiceAsync(result.Id); + Assert.NotNull(deleteResult); Assert.IsTrue(deleteResult); } } diff --git a/ElevenLabs-DotNet/ElevenLabsAuthentication.cs b/ElevenLabs-DotNet/Authentication/ElevenLabsAuthentication.cs similarity index 92% rename from ElevenLabs-DotNet/ElevenLabsAuthentication.cs rename to ElevenLabs-DotNet/Authentication/ElevenLabsAuthentication.cs index 1c6683a..d33bc86 100644 --- a/ElevenLabs-DotNet/ElevenLabsAuthentication.cs +++ b/ElevenLabs-DotNet/Authentication/ElevenLabsAuthentication.cs @@ -6,9 +6,13 @@ namespace ElevenLabs { + /// + /// Represents authentication for ElevenLabs + /// public sealed class ElevenLabsAuthentication { - private const string ELEVEN_LABS_API_KEY = "ELEVEN_LABS_API_KEY"; + internal const string CONFIG_FILE = ".elevenlabs"; + private const string ELEVEN_LABS_API_KEY = nameof(ELEVEN_LABS_API_KEY); private readonly AuthInfo authInfo; @@ -100,13 +104,18 @@ public static ElevenLabsAuthentication LoadFromPath(string path) /// or if it was not successful in finding a config /// (or if the config file didn't contain correctly formatted API keys) /// - public static ElevenLabsAuthentication LoadFromDirectory(string directory = null, string filename = ".elevenlabs", bool searchUp = true) + public static ElevenLabsAuthentication LoadFromDirectory(string directory = null, string filename = CONFIG_FILE, bool searchUp = true) { if (string.IsNullOrWhiteSpace(directory)) { directory = Environment.CurrentDirectory; } + if (string.IsNullOrWhiteSpace(filename)) + { + filename = CONFIG_FILE; + } + AuthInfo authInfo = null; var currentDirectory = new DirectoryInfo(directory); @@ -161,13 +170,7 @@ public static ElevenLabsAuthentication LoadFromDirectory(string directory = null } } - if (authInfo == null || - string.IsNullOrEmpty(authInfo.ApiKey)) - { - return null; - } - - return new ElevenLabsAuthentication(authInfo); + return string.IsNullOrEmpty(authInfo?.ApiKey) ? null : new ElevenLabsAuthentication(authInfo); } } } diff --git a/ElevenLabs-DotNet/ElevenLabsClientSettings.cs b/ElevenLabs-DotNet/Authentication/ElevenLabsClientSettings.cs similarity index 100% rename from ElevenLabs-DotNet/ElevenLabsClientSettings.cs rename to ElevenLabs-DotNet/Authentication/ElevenLabsClientSettings.cs diff --git a/ElevenLabs-DotNet/BaseEndPoint.cs b/ElevenLabs-DotNet/Common/ElevenLabsBaseEndPoint.cs similarity index 69% rename from ElevenLabs-DotNet/BaseEndPoint.cs rename to ElevenLabs-DotNet/Common/ElevenLabsBaseEndPoint.cs index d3c8a2e..1c950f1 100644 --- a/ElevenLabs-DotNet/BaseEndPoint.cs +++ b/ElevenLabs-DotNet/Common/ElevenLabsBaseEndPoint.cs @@ -5,11 +5,12 @@ namespace ElevenLabs { - public abstract class BaseEndPoint + public abstract class ElevenLabsBaseEndPoint { - internal BaseEndPoint(ElevenLabsClient api) => Api = api; + internal ElevenLabsBaseEndPoint(ElevenLabsClient client) => this.client = client; - protected readonly ElevenLabsClient Api; + // ReSharper disable once InconsistentNaming + protected readonly ElevenLabsClient client; /// /// The root endpoint address. @@ -23,7 +24,7 @@ public abstract class BaseEndPoint /// Optional, parameters to add to the endpoint. protected string GetUrl(string endpoint = "", Dictionary queryParameters = null) { - var result = string.Format(Api.ElevenLabsClientSettings.BaseRequestUrlFormat, $"{Root}{endpoint}"); + var result = string.Format(client.ElevenLabsClientSettings.BaseRequestUrlFormat, $"{Root}{endpoint}"); if (queryParameters is { Count: not 0 }) { @@ -33,10 +34,16 @@ protected string GetUrl(string endpoint = "", Dictionary queryPa return result; } + private bool enableDebug; + /// /// Enables or disables the logging of all http responses of header and body information for this endpoint.
/// WARNING! Enabling this in your production build, could potentially leak sensitive information! ///
- public bool EnableDebug { get; set; } + public bool EnableDebug + { + get => enableDebug || client.EnableDebug; + set => enableDebug = value; + } } } diff --git a/ElevenLabs-DotNet/OutputFormat.cs b/ElevenLabs-DotNet/Common/OutputFormat.cs similarity index 100% rename from ElevenLabs-DotNet/OutputFormat.cs rename to ElevenLabs-DotNet/Common/OutputFormat.cs diff --git a/ElevenLabs-DotNet/VoiceClip.cs b/ElevenLabs-DotNet/Common/VoiceClip.cs similarity index 100% rename from ElevenLabs-DotNet/VoiceClip.cs rename to ElevenLabs-DotNet/Common/VoiceClip.cs diff --git a/ElevenLabs-DotNet/ElevenLabs-DotNet.csproj b/ElevenLabs-DotNet/ElevenLabs-DotNet.csproj index bcb1106..a22cdd6 100644 --- a/ElevenLabs-DotNet/ElevenLabs-DotNet.csproj +++ b/ElevenLabs-DotNet/ElevenLabs-DotNet.csproj @@ -14,8 +14,12 @@ RageAgainstThePixel 2023 ElevenLabs, AI, ML, Voice, TTS - 2.0.3 - Version 2.0.3 + 2.1.0 + Version 2.1.0 +- Added ElevenLabsClient.EnableDebug option to enable and disable for all endpoints +- Synced changes from unity package +- Updated unit test +Version 2.0.3 - Fixed text to speech streaming Version 2.0.2 - Added the u-law format diff --git a/ElevenLabs-DotNet/ElevenLabsClient.cs b/ElevenLabs-DotNet/ElevenLabsClient.cs index d5ab5ce..aa33a62 100644 --- a/ElevenLabs-DotNet/ElevenLabsClient.cs +++ b/ElevenLabs-DotNet/ElevenLabsClient.cs @@ -19,9 +19,12 @@ public sealed class ElevenLabsClient /// Creates a new client for the Eleven Labs API, handling auth and allowing for access to various API endpoints. ///
/// The API authentication information to use for API calls, - /// or to attempt to use the , - /// potentially loading from environment vars or from a config file. - /// Optional, for specifying a proxy domain. + /// or to attempt to use the , + /// potentially loading from environment vars or from a config file. + /// + /// + /// Optional, for specifying a proxy domain. + /// /// Optional, . /// Raised when authentication details are missing or invalid. public ElevenLabsClient(ElevenLabsAuthentication elevenLabsAuthentication = null, ElevenLabsClientSettings clientSettings = null, HttpClient httpClient = null) @@ -59,6 +62,11 @@ public ElevenLabsClient(ElevenLabsAuthentication elevenLabsAuthentication = null DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; + /// + /// Enables or disables debugging for all endpoints. + /// + public bool EnableDebug { get; set; } + /// /// The API authentication information to use for API calls /// diff --git a/ElevenLabs-DotNet/History/HistoryEndpoint.cs b/ElevenLabs-DotNet/History/HistoryEndpoint.cs index 290bfe4..a97b0a6 100644 --- a/ElevenLabs-DotNet/History/HistoryEndpoint.cs +++ b/ElevenLabs-DotNet/History/HistoryEndpoint.cs @@ -15,16 +15,19 @@ namespace ElevenLabs.History /// /// Access to your history. Your history is a list of all your created audio including its metadata. /// - public sealed class HistoryEndpoint : BaseEndPoint + public sealed class HistoryEndpoint : ElevenLabsBaseEndPoint { - public HistoryEndpoint(ElevenLabsClient api) : base(api) { } + public HistoryEndpoint(ElevenLabsClient client) : base(client) { } protected override string Root => "history"; /// /// Get metadata about all your generated audio. /// - /// Optional, number of items to return. Cannot exceed 1000. + /// + /// Optional, number of items to return. Cannot exceed 1000.
+ /// Default: 100 + /// /// Optional, the id of the item to start after. /// Optional, . /// . @@ -42,7 +45,7 @@ public async Task GetHistoryAsync(int? pageSize = null, string star parameters.Add("start_after_history_item_id", startAfterId); } - var response = await Api.Client.GetAsync(GetUrl(queryParameters: parameters), cancellationToken); + var response = await client.Client.GetAsync(GetUrl(queryParameters: parameters), cancellationToken); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken: cancellationToken); return JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions); } @@ -55,7 +58,7 @@ public async Task GetHistoryAsync(int? pageSize = null, string star /// public async Task GetHistoryItemAsync(string id, CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl($"/{id}"), cancellationToken); + var response = await client.Client.GetAsync(GetUrl($"/{id}"), cancellationToken); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken: cancellationToken); return JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions); } @@ -68,8 +71,8 @@ public async Task GetHistoryItemAsync(string id, CancellationToken /// . public async Task DownloadHistoryAudioAsync(HistoryItem historyItem, CancellationToken cancellationToken = default) { - var voice = await Api.VoicesEndpoint.GetVoiceAsync(historyItem.VoiceId, cancellationToken: cancellationToken).ConfigureAwait(false); - var response = await Api.Client.GetAsync(GetUrl($"/{historyItem.Id}/audio"), cancellationToken).ConfigureAwait(false); + var voice = await client.VoicesEndpoint.GetVoiceAsync(historyItem.VoiceId, cancellationToken: cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{historyItem.Id}/audio"), cancellationToken).ConfigureAwait(false); await response.CheckResponseAsync(cancellationToken); var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); var memoryStream = new MemoryStream(); @@ -97,14 +100,14 @@ public async Task DownloadHistoryAudioAsync(HistoryItem historyItem, /// True, if history item was successfully deleted. public async Task DeleteHistoryItemAsync(string id, CancellationToken cancellationToken = default) { - var response = await Api.Client.DeleteAsync(GetUrl($"/{id}"), cancellationToken); + var response = await client.Client.DeleteAsync(GetUrl($"/{id}"), cancellationToken); await response.ReadAsStringAsync(EnableDebug, cancellationToken: cancellationToken); return response.IsSuccessStatusCode; } /// /// Download one or more history items.
- /// If no ids are specified, then all history items are downloaded.
+ /// If no ids are specified, then the last 100 history items are downloaded.
/// If one history item id is provided, we will return a single audio file.
/// If more than one history item ids are provided multiple audio files will be downloaded. ///
diff --git a/ElevenLabs-DotNet/Models/ModelsEndpoint.cs b/ElevenLabs-DotNet/Models/ModelsEndpoint.cs index 48f4abb..de49022 100644 --- a/ElevenLabs-DotNet/Models/ModelsEndpoint.cs +++ b/ElevenLabs-DotNet/Models/ModelsEndpoint.cs @@ -7,9 +7,9 @@ namespace ElevenLabs.Models { - public sealed class ModelsEndpoint : BaseEndPoint + public sealed class ModelsEndpoint : ElevenLabsBaseEndPoint { - public ModelsEndpoint(ElevenLabsClient api) : base(api) { } + public ModelsEndpoint(ElevenLabsClient client) : base(client) { } protected override string Root => "models"; @@ -19,7 +19,7 @@ public ModelsEndpoint(ElevenLabsClient api) : base(api) { } /// A list of s you can use. public async Task> GetModelsAsync() { - var response = await Api.Client.GetAsync(GetUrl()); + var response = await client.Client.GetAsync(GetUrl()); var responseAsString = await response.ReadAsStringAsync(EnableDebug); return JsonSerializer.Deserialize>(responseAsString, ElevenLabsClient.JsonSerializationOptions); } diff --git a/ElevenLabs-DotNet/TextToSpeech/TextToSpeechEndpoint.cs b/ElevenLabs-DotNet/TextToSpeech/TextToSpeechEndpoint.cs index df9845e..c32b5fc 100644 --- a/ElevenLabs-DotNet/TextToSpeech/TextToSpeechEndpoint.cs +++ b/ElevenLabs-DotNet/TextToSpeech/TextToSpeechEndpoint.cs @@ -17,13 +17,13 @@ namespace ElevenLabs.TextToSpeech /// /// Access to convert text to synthesized speech. /// - public sealed class TextToSpeechEndpoint : BaseEndPoint + public sealed class TextToSpeechEndpoint : ElevenLabsBaseEndPoint { private const string HistoryItemId = "history-item-id"; private const string OutputFormatParameter = "output_format"; private const string OptimizeStreamingLatencyParameter = "optimize_streaming_latency"; - public TextToSpeechEndpoint(ElevenLabsClient api) : base(api) { } + public TextToSpeechEndpoint(ElevenLabsClient client) : base(client) { } protected override string Root => "text-to-speech"; @@ -76,7 +76,7 @@ public async Task TextToSpeechAsync(string text, Voice voice, VoiceSe throw new ArgumentNullException(nameof(voice)); } - var defaultVoiceSettings = voiceSettings ?? voice.Settings ?? await Api.VoicesEndpoint.GetDefaultVoiceSettingsAsync(cancellationToken); + var defaultVoiceSettings = voiceSettings ?? voice.Settings ?? await client.VoicesEndpoint.GetDefaultVoiceSettingsAsync(cancellationToken); var payload = JsonSerializer.Serialize(new TextToSpeechRequest(text, model, defaultVoiceSettings)).ToJsonStringContent(); var parameters = new Dictionary { @@ -93,7 +93,7 @@ public async Task TextToSpeechAsync(string text, Voice voice, VoiceSe var requestOption = partialClipCallback == null ? HttpCompletionOption.ResponseContentRead : HttpCompletionOption.ResponseHeadersRead; - var response = await Api.Client.SendAsync(postRequest, requestOption, cancellationToken); + var response = await client.Client.SendAsync(postRequest, requestOption, cancellationToken); await response.CheckResponseAsync(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 37a3189..bb0451f 100644 --- a/ElevenLabs-DotNet/Users/UserEndpoint.cs +++ b/ElevenLabs-DotNet/Users/UserEndpoint.cs @@ -9,11 +9,10 @@ namespace ElevenLabs.User /// /// Access to your user account information. /// - public sealed class UserEndpoint : BaseEndPoint + public sealed class UserEndpoint : ElevenLabsBaseEndPoint { - public UserEndpoint(ElevenLabsClient api) : base(api) { } + public UserEndpoint(ElevenLabsClient client) : base(client) { } - /// protected override string Root => "user"; /// @@ -21,7 +20,7 @@ public UserEndpoint(ElevenLabsClient api) : base(api) { } /// public async Task GetUserInfoAsync() { - var response = await Api.Client.GetAsync(GetUrl()); + var response = await client.Client.GetAsync(GetUrl()); var responseAsString = await response.ReadAsStringAsync(EnableDebug); return JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions); } @@ -31,7 +30,7 @@ public async Task GetUserInfoAsync() /// public async Task GetSubscriptionInfoAsync() { - var response = await Api.Client.GetAsync(GetUrl("/subscription")); + var response = await client.Client.GetAsync(GetUrl("/subscription")); var responseAsString = await response.ReadAsStringAsync(EnableDebug); return JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions); } diff --git a/ElevenLabs-DotNet/VoiceGeneration/VoiceGenerationEndpoint.cs b/ElevenLabs-DotNet/VoiceGeneration/VoiceGenerationEndpoint.cs index 08d3fc8..e4ec9bb 100644 --- a/ElevenLabs-DotNet/VoiceGeneration/VoiceGenerationEndpoint.cs +++ b/ElevenLabs-DotNet/VoiceGeneration/VoiceGenerationEndpoint.cs @@ -11,9 +11,9 @@ namespace ElevenLabs.VoiceGeneration { - public sealed class VoiceGenerationEndpoint : BaseEndPoint + public sealed class VoiceGenerationEndpoint : ElevenLabsBaseEndPoint { - public VoiceGenerationEndpoint(ElevenLabsClient api) : base(api) { } + public VoiceGenerationEndpoint(ElevenLabsClient client) : base(client) { } protected override string Root => "voice-generation"; @@ -24,7 +24,7 @@ public VoiceGenerationEndpoint(ElevenLabsClient api) : base(api) { } /// . public async Task GetVoiceGenerationOptionsAsync(CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl("/generate-voice/parameters"), cancellationToken); + var response = await client.Client.GetAsync(GetUrl("/generate-voice/parameters"), cancellationToken); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken: cancellationToken); return JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions); } @@ -38,7 +38,7 @@ public async Task GetVoiceGenerationOptionsAsync(Cancella public async Task>> GenerateVoicePreviewAsync(GeneratedVoicePreviewRequest generatedVoicePreviewRequest, CancellationToken cancellationToken = default) { var payload = JsonSerializer.Serialize(generatedVoicePreviewRequest, ElevenLabsClient.JsonSerializationOptions).ToJsonStringContent(); - var response = await Api.Client.PostAsync(GetUrl("/generate-voice"), payload, cancellationToken); + var response = await client.Client.PostAsync(GetUrl("/generate-voice"), payload, cancellationToken); await response.CheckResponseAsync(cancellationToken); var generatedVoiceId = response.Headers.FirstOrDefault(pair => pair.Key == "generated_voice_id").Value.FirstOrDefault(); await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); @@ -65,7 +65,7 @@ public async Task>> GenerateVoicePreviewAsync public async Task CreateVoiceAsync(CreateVoiceRequest createVoiceRequest, CancellationToken cancellationToken = default) { var payload = JsonSerializer.Serialize(createVoiceRequest).ToJsonStringContent(); - var response = await Api.Client.PostAsync(GetUrl("/create-voice"), payload, cancellationToken); + var response = await client.Client.PostAsync(GetUrl("/create-voice"), payload, cancellationToken); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken: cancellationToken); return JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions); } diff --git a/ElevenLabs-DotNet/Voices/VoiceSettings.cs b/ElevenLabs-DotNet/Voices/VoiceSettings.cs index 3ff9990..8c9ce9a 100644 --- a/ElevenLabs-DotNet/Voices/VoiceSettings.cs +++ b/ElevenLabs-DotNet/Voices/VoiceSettings.cs @@ -7,7 +7,11 @@ namespace ElevenLabs.Voices public sealed class VoiceSettings { [JsonConstructor] - public VoiceSettings(float stability, float similarityBoost, bool speakerBoost = true, float style = 0.45f) + public VoiceSettings( + float stability = .75f, + float similarityBoost = .75f, + bool speakerBoost = true, + float style = 0.45f) { Stability = stability; SimilarityBoost = similarityBoost; diff --git a/ElevenLabs-DotNet/Voices/VoicesEndpoint.cs b/ElevenLabs-DotNet/Voices/VoicesEndpoint.cs index 95a39a4..d4134ff 100644 --- a/ElevenLabs-DotNet/Voices/VoicesEndpoint.cs +++ b/ElevenLabs-DotNet/Voices/VoicesEndpoint.cs @@ -16,7 +16,7 @@ namespace ElevenLabs.Voices /// /// Access to voices created either by you or us. /// - public sealed class VoicesEndpoint : BaseEndPoint + public sealed class VoicesEndpoint : ElevenLabsBaseEndPoint { private class VoiceList { @@ -32,7 +32,7 @@ private class VoiceResponse public string VoiceId { get; private set; } } - public VoicesEndpoint(ElevenLabsClient api) : base(api) { } + public VoicesEndpoint(ElevenLabsClient client) : base(client) { } protected override string Root => "voices"; @@ -43,7 +43,7 @@ public VoicesEndpoint(ElevenLabsClient api) : base(api) { } /// of s. public async Task> GetAllVoicesAsync(CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl(), cancellationToken).ConfigureAwait(false); + 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; var voiceSettingsTasks = new List(); @@ -69,7 +69,7 @@ async Task LocalGetVoiceSettings() /// . public async Task GetDefaultVoiceSettingsAsync(CancellationToken cancellationToken = default) { - var response = await Api.Client.GetAsync(GetUrl("/settings/default"), cancellationToken).ConfigureAwait(false); + 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); } @@ -87,7 +87,7 @@ public async Task GetVoiceSettingsAsync(string voiceId, Cancellat throw new ArgumentNullException(nameof(voiceId)); } - var response = await Api.Client.GetAsync(GetUrl($"/{voiceId}/settings"), cancellationToken).ConfigureAwait(false); + 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); } @@ -106,7 +106,7 @@ public async Task GetVoiceAsync(string voiceId, bool withSettings = false throw new ArgumentNullException(nameof(voiceId)); } - var response = await Api.Client.GetAsync(GetUrl($"/{voiceId}?with_settings={withSettings}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{voiceId}?with_settings={withSettings}"), cancellationToken).ConfigureAwait(false); var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return JsonSerializer.Deserialize(responseAsString, ElevenLabsClient.JsonSerializationOptions); } @@ -126,7 +126,7 @@ public async Task EditVoiceSettingsAsync(string voiceId, VoiceSettings voi } var payload = JsonSerializer.Serialize(voiceSettings).ToJsonStringContent(); - var response = await Api.Client.PostAsync(GetUrl($"/{voiceId}/settings/edit"), payload, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl($"/{voiceId}/settings/edit"), payload, cancellationToken).ConfigureAwait(false); await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false); return response.IsSuccessStatusCode; } @@ -181,7 +181,7 @@ public async Task AddVoiceAsync(string name, IEnumerable samplePa form.Add(new StringContent(JsonSerializer.Serialize(labels)), "labels"); } - var response = await Api.Client.PostAsync(GetUrl("/add"), form, cancellationToken).ConfigureAwait(false); + 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); @@ -238,7 +238,7 @@ public async Task EditVoiceAsync(Voice voice, IEnumerable samplePa form.Add(new StringContent(JsonSerializer.Serialize(labels)), "labels"); } - var response = await Api.Client.PostAsync(GetUrl($"/{voice.Id}/edit"), form, cancellationToken).ConfigureAwait(false); + var response = await client.Client.PostAsync(GetUrl($"/{voice.Id}/edit"), form, cancellationToken).ConfigureAwait(false); await response.CheckResponseAsync(cancellationToken).ConfigureAwait(false); return response.IsSuccessStatusCode; } @@ -256,7 +256,7 @@ public async Task DeleteVoiceAsync(string voiceId, CancellationToken cance throw new ArgumentNullException(nameof(voiceId)); } - var response = await Api.Client.DeleteAsync(GetUrl($"/{voiceId}"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.DeleteAsync(GetUrl($"/{voiceId}"), cancellationToken).ConfigureAwait(false); await response.CheckResponseAsync(cancellationToken).ConfigureAwait(false); return response.IsSuccessStatusCode; } @@ -264,10 +264,10 @@ public async Task DeleteVoiceAsync(string voiceId, CancellationToken cance #region Samples /// - /// Downloads the audio corresponding to a <see cref="Sample"/> attached to a <see cref="Voice"/>. + /// Download the audio corresponding to a attached to a . /// /// The this belongs to. - /// The to download audio for. + /// The id to download. /// Optional, . /// . public async Task DownloadVoiceSampleAudioAsync(Voice voice, Sample sample, CancellationToken cancellationToken = default) @@ -284,7 +284,7 @@ public async Task DownloadVoiceSampleAudioAsync(Voice voice, Sample s throw new ArgumentNullException(nameof(sample)); } - var response = await Api.Client.GetAsync(GetUrl($"/{voice.Id}/samples/{sample.Id}/audio"), cancellationToken).ConfigureAwait(false); + var response = await client.Client.GetAsync(GetUrl($"/{voice.Id}/samples/{sample.Id}/audio"), cancellationToken).ConfigureAwait(false); await response.CheckResponseAsync(cancellationToken).ConfigureAwait(false); await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await using var memoryStream = new MemoryStream(); @@ -311,7 +311,7 @@ public async Task DeleteVoiceSampleAsync(string voiceId, string sampleId, throw new ArgumentNullException(nameof(sampleId)); } - var response = await Api.Client.DeleteAsync(GetUrl($"/{voiceId}/samples/{sampleId}"), cancellationToken); + var response = await client.Client.DeleteAsync(GetUrl($"/{voiceId}/samples/{sampleId}"), cancellationToken); await response.CheckResponseAsync(cancellationToken); return response.IsSuccessStatusCode; } diff --git a/README.md b/README.md index 668f11a..2ef2ffb 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ [![NuGet version (ElevenLabs-DotNet-Proxy)](https://img.shields.io/nuget/v/ElevenLabs-DotNet-Proxy.svg?label=ElevenLabs-DotNet-Proxy&logo=nuget)](https://www.nuget.org/packages/ElevenLabs-DotNet-Proxy/) [![Nuget Publish](https://github.com/RageAgainstThePixel/ElevenLabs-DotNet/actions/workflows/Publish-Nuget.yml/badge.svg)](https://github.com/RageAgainstThePixel/ElevenLabs-DotNet/actions/workflows/Publish-Nuget.yml) -A non-official [Eleven Labs](https://elevenlabs.io/) voice synthesis RESTful client. +A non-official [Eleven Labs](https://elevenlabs.io/?from=partnerbrown9849) voice synthesis RESTful client. -I am not affiliated with Eleven Labs and an account with api access is required. +I am not affiliated with ElevenLabs and an account with api access is required. ***All copyrights, trademarks, logos, and assets are the property of their respective owners.*** @@ -15,7 +15,7 @@ I am not affiliated with Eleven Labs and an account with api access is required. ### Install from NuGet -Install package [`ElevenLabs` from Nuget](https://www.nuget.org/packages/ElevenLabs-DotNet/). Here's how via command line: +Install package [`ElevenLabs-DotNet` from Nuget](https://www.nuget.org/packages/ElevenLabs-DotNet/). Here's how via command line: ```powershell Install-Package ElevenLabs-DotNet @@ -34,7 +34,7 @@ Install-Package ElevenLabs-DotNet - [Authentication](#authentication) - [API Proxy](#api-proxy) - [Text to Speech](#text-to-speech) - - [Stream Text To Speech](#stream-text-to-speech) :new: + - [Stream Text To Speech](#stream-text-to-speech) - [Voices](#voices) - [Get All Voices](#get-all-voices) - [Get Default Voice Settings](#get-default-voice-settings) @@ -44,13 +44,13 @@ Install-Package ElevenLabs-DotNet - [Edit Voice](#edit-voice) - [Delete Voice](#delete-voice) - [Samples](#samples) - - [Download Voice Sample](#download-voice-sample) :new: + - [Download Voice Sample](#download-voice-sample) - [Delete Voice Sample](#delete-voice-sample) - [History](#history) - [Get History](#get-history) - [Get History Item](#get-history-item) - - [Download History Audio](#download-history-audio) :new: - - [Download History Items](#download-history-items) :new: + - [Download History Audio](#download-history-audio) + - [Download History Items](#download-history-items) - [Delete History Item](#delete-history-item) - [User](#user) - [Get User Info](#get-user-info) @@ -112,7 +112,7 @@ var api = new ElevenLabsClient(ElevenLabsAuthentication.LoadFromEnv()); Using either the [ElevenLabs-DotNet](https://github.com/RageAgainstThePixel/ElevenLabs-DotNet) or [com.rest.elevenlabs](https://github.com/RageAgainstThePixel/com.rest.elevenlabs) packages directly in your front-end app may expose your API keys and other sensitive information. To mitigate this risk, it is recommended to set up an intermediate API that makes requests to ElevenLabs on behalf of your front-end app. This library can be utilized for both front-end and intermediary host configurations, ensuring secure communication with the ElevenLabs API. -### Front End Example +#### Front End Example In the front end example, you will need to securely authenticate your users using your preferred OAuth provider. Once the user is authenticated, exchange your custom auth token with your API key on the backend. @@ -135,7 +135,7 @@ var api = new ElevenLabsClient(auth, settings); This setup allows your front end application to securely communicate with your backend that will be using the ElevenLabs-DotNet-Proxy, which then forwards requests to the ElevenLabs API. This ensures that your ElevenLabs API keys and other sensitive information remain secure throughout the process. -### Back End Example +#### Back End Example In this example, we demonstrate how to set up and use `ElevenLabsProxyStartup` in a new ASP.NET Core web app. The proxy server will handle authentication and forward requests to the ElevenLabs API, ensuring that your API keys and other sensitive information remain secure. @@ -182,7 +182,8 @@ Convert text to speech. var api = new ElevenLabsClient(); var text = "The quick brown fox jumps over the lazy dog."; var voice = (await api.VoicesEndpoint.GetAllVoicesAsync()).FirstOrDefault(); -var voiceClip = await api.TextToSpeechEndpoint.TextToSpeechAsync(text, voice); +var defaultVoiceSettings = await api.VoicesEndpoint.GetDefaultVoiceSettingsAsync(); +var voiceClip = await api.TextToSpeechEndpoint.TextToSpeechAsync(text, voice, defaultVoiceSettings); await File.WriteAllBytesAsync($"{voiceClip.Id}.mp3", voiceClip.ClipData.ToArray()); ``` @@ -310,18 +311,22 @@ Access to your previously synthesized audio clips including its metadata. #### Get History +Get metadata about all your generated audio. + ```csharp var api = new ElevenLabsClient(); var historyItems = await api.HistoryEndpoint.GetHistoryAsync(); -foreach (var historyItem in historyItems.OrderBy(historyItem => historyItem.Date)) +foreach (var item in historyItems.OrderBy(historyItem => historyItem.Date)) { - Console.WriteLine($"{historyItem.State} {historyItem.Date} | {historyItem.Id} | {historyItem.Text.Length} | {historyItem.Text}"); + Console.WriteLine($"{item.State} {item.Date} | {item.Id} | {item.Text.Length} | {item.Text}"); } ``` #### Get History Item +Get information about a specific item. + ```csharp var api = new ElevenLabsClient(); var historyItem = await api.HistoryEndpoint.GetHistoryItemAsync(voiceClip.Id); @@ -337,9 +342,11 @@ await File.WriteAllBytesAsync($"{voiceClip.Id}.mp3", voiceClip.ClipData.ToArray( #### Download History Items +Downloads the last 100 history items, or the collection of specified items. + ```csharp var api = new ElevenLabsClient(); -var historyInfo = await api.HistoryEndpoint.DownloadHistoryItemsAsync(); +var voiceClips = await api.HistoryEndpoint.DownloadHistoryItemsAsync(); ``` #### Delete History Item