Skip to content

Commit

Permalink
ElevenLabs-DotNet 2.0.0 (#26)
Browse files Browse the repository at this point in the history
Refactoring to support latest API changes

- Biggest Change is the new VoiceClip signature for all endpoints which contains all the information about the generated clip.
- Refactored HistoryEndpoint
  - Made HistoryInfo public
- GetHistoryAsync now returns HistoryInfo and contains additional pageSize and startAfter properties
  - Added GetHistoryItemAsync
  - Renamed GetHistoryAudioAsync -> DownloadHistoryAudioAsync
- DownloadHistoryItemsAsync now returns a list of VoiceClips, and no longer asks for saveDirectory
- HistoryItem.TextHash was modified to generate hash based on item id, instead of voiceId
- Refactored ModelsEndpoint
  - Added Model.MultiLingualV2
- Refactored TextToSpeechEndpoint
  - Refactored TextToSpeechAsync
    - Removed saveDirectory parameter
    - Removed deleteCachedFile parameter
    - Added outputFormat parameter
    - Changed return type to VoiceClip
  - Refactored StreamTextToSpeechAsync
    - Removed saveDirectory parameter
    - Removed deleteCachedFile parameter
    -  Added  outputFormat
- Added partialClipCallback for queuing and playing partial clips as they are received
- Refactored VoiceGenerationEndpoint
  - Renamed GenerateVoiceAsync -> GenerateVoicePreviewAsync
    - Removed saveDirectory parameter
  - Renamed GeneratedVoiceRequest -> GeneratedVoicePreviewRequest
- Refactored VoicesEndpoint
  - Preserve default values in VoiceSettings
  - Refactored GetVoiceAsync
    - withSettings parameter is now false by default per API
  - Renamed GetVoiceSampleAsync -> DownloadVoiceSampleAsync
    - Changed return type to VoiceClip
    - Removed saveDirectory parameter
  • Loading branch information
StephenHodgson authored Oct 29, 2023
1 parent 76d4407 commit 38cd9a7
Show file tree
Hide file tree
Showing 25 changed files with 491 additions and 388 deletions.
1 change: 0 additions & 1 deletion ElevenLabs-DotNet-Proxy/Proxy/ElevenLabsProxyStartup.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using ElevenLabs.Proxy;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
Expand Down
2 changes: 1 addition & 1 deletion ElevenLabs-DotNet-Tests/Test_Fixture_02_VoicesEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public async Task Test_07_GetVoiceSample()
Assert.IsNotEmpty(updatedVoice.Samples);
var sample = updatedVoice.Samples.FirstOrDefault();
Assert.NotNull(sample);
var result = await ElevenLabsClient.VoicesEndpoint.GetVoiceSampleAsync(updatedVoice, updatedVoice.Samples.FirstOrDefault());
var result = await ElevenLabsClient.VoicesEndpoint.DownloadVoiceSampleAudioAsync(updatedVoice, sample);
Assert.NotNull(result);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public async Task Test_01_TextToSpeech()
var voice = (await ElevenLabsClient.VoicesEndpoint.GetAllVoicesAsync()).FirstOrDefault();
Assert.NotNull(voice);
var defaultVoiceSettings = await ElevenLabsClient.VoicesEndpoint.GetDefaultVoiceSettingsAsync();
var clipPath = await ElevenLabsClient.TextToSpeechEndpoint.TextToSpeechAsync("The quick brown fox jumps over the lazy dog.", voice, defaultVoiceSettings, deleteCachedFile: true);
var clipPath = await ElevenLabsClient.TextToSpeechEndpoint.TextToSpeechAsync("The quick brown fox jumps over the lazy dog.", voice, defaultVoiceSettings);
Assert.NotNull(clipPath);
Console.WriteLine(clipPath);
}
Expand Down
48 changes: 24 additions & 24 deletions ElevenLabs-DotNet-Tests/Test_Fixture_04_HistoryEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ public async Task Test_01_GetHistory()
Assert.NotNull(ElevenLabsClient.HistoryEndpoint);
var results = await ElevenLabsClient.HistoryEndpoint.GetHistoryAsync();
Assert.NotNull(results);
Assert.IsNotEmpty(results);
Assert.IsNotEmpty(results.HistoryItems);

foreach (var item in results.OrderBy(item => item.Date))
foreach (var item in results.HistoryItems.OrderBy(item => item.Date))
{
Console.WriteLine($"{item.State} {item.Date} | {item.Id} | {item.Text.Length} | {item.Text}");
}
Expand All @@ -28,41 +28,41 @@ public async Task Test_01_GetHistory()
public async Task Test_02_GetHistoryAudio()
{
Assert.NotNull(ElevenLabsClient.HistoryEndpoint);
var historyItems = await ElevenLabsClient.HistoryEndpoint.GetHistoryAsync();
Assert.NotNull(historyItems);
Assert.IsNotEmpty(historyItems);
var downloadItem = historyItems.MaxBy(item => item.Date);
var historyInfo = await ElevenLabsClient.HistoryEndpoint.GetHistoryAsync();
Assert.NotNull(historyInfo);
Assert.IsNotEmpty(historyInfo.HistoryItems);
var downloadItem = historyInfo.HistoryItems.MaxBy(item => item.Date);
Assert.NotNull(downloadItem);
Console.WriteLine($"Downloading {downloadItem!.Id}...");
var result = await ElevenLabsClient.HistoryEndpoint.GetHistoryAudioAsync(downloadItem);
Assert.NotNull(result);
var voiceClip = await ElevenLabsClient.HistoryEndpoint.DownloadHistoryAudioAsync(downloadItem);
Assert.NotNull(voiceClip);
}

[Test]
public async Task Test_03_DownloadAllHistoryItems()
{
Assert.NotNull(ElevenLabsClient.HistoryEndpoint);
var historyItems = await ElevenLabsClient.HistoryEndpoint.GetHistoryAsync();
Assert.NotNull(historyItems);
Assert.IsNotEmpty(historyItems);
var singleItem = historyItems.FirstOrDefault();
var historyInfo = await ElevenLabsClient.HistoryEndpoint.GetHistoryAsync();
Assert.NotNull(historyInfo);
Assert.IsNotEmpty(historyInfo.HistoryItems);
var singleItem = historyInfo.HistoryItems.FirstOrDefault();
var singleItemResult = await ElevenLabsClient.HistoryEndpoint.DownloadHistoryItemsAsync(new List<string> { singleItem });
Assert.NotNull(singleItemResult);
Assert.IsNotEmpty(singleItemResult);
var downloadItems = historyItems.Select(item => item.Id).ToList();
var results = await ElevenLabsClient.HistoryEndpoint.DownloadHistoryItemsAsync(downloadItems);
Assert.NotNull(results);
Assert.IsNotEmpty(results);
var downloadItems = historyInfo.HistoryItems.Select(item => item.Id).ToList();
var voiceClips = await ElevenLabsClient.HistoryEndpoint.DownloadHistoryItemsAsync(downloadItems);
Assert.NotNull(voiceClips);
Assert.IsNotEmpty(voiceClips);
}

[Test]
public async Task Test_04_DeleteHistoryItem()
{
Assert.NotNull(ElevenLabsClient.HistoryEndpoint);
var historyItems = await ElevenLabsClient.HistoryEndpoint.GetHistoryAsync();
Assert.NotNull(historyItems);
Assert.IsNotEmpty(historyItems);
var itemsToDelete = historyItems.Where(item => item.Text.Contains("The quick brown fox jumps over the lazy dog.")).ToList();
var historyInfo = await ElevenLabsClient.HistoryEndpoint.GetHistoryAsync();
Assert.NotNull(historyInfo);
Assert.IsNotEmpty(historyInfo.HistoryItems);
var itemsToDelete = historyInfo.HistoryItems.Where(item => item.Text.Contains("The quick brown fox jumps over the lazy dog.")).ToList();
Assert.NotNull(itemsToDelete);
Assert.IsNotEmpty(itemsToDelete);

Expand All @@ -74,11 +74,11 @@ public async Task Test_04_DeleteHistoryItem()
Assert.IsTrue(result);
}

var updatedItems = await ElevenLabsClient.HistoryEndpoint.GetHistoryAsync();
Assert.NotNull(updatedItems);
Assert.That(updatedItems, Has.None.EqualTo(itemsToDelete));
var updatedHistoryInfo = await ElevenLabsClient.HistoryEndpoint.GetHistoryAsync();
Assert.NotNull(updatedHistoryInfo);
Assert.That(updatedHistoryInfo.HistoryItems, Has.None.EqualTo(itemsToDelete));

foreach (var item in updatedItems.OrderBy(item => item.Date))
foreach (var item in updatedHistoryInfo.HistoryItems.OrderBy(item => item.Date))
{
Console.WriteLine($"{item.State} {item.Date} | {item.Id} | {item.Text}");
}
Expand Down
9 changes: 3 additions & 6 deletions ElevenLabs-DotNet-Tests/Test_Fixture_05_VoiceGeneration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using ElevenLabs.VoiceGeneration;
using NUnit.Framework;
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
Expand All @@ -26,18 +25,16 @@ public async Task Test_02_GenerateVoice()
{
Assert.NotNull(ElevenLabsClient.VoiceGenerationEndpoint);
var options = await ElevenLabsClient.VoiceGenerationEndpoint.GetVoiceGenerationOptionsAsync();
var generateRequest = new GeneratedVoiceRequest("First we thought the PC was a calculator. Then we found out how to turn numbers into letters and we thought it was a typewriter.", options.Genders.FirstOrDefault(), options.Accents.FirstOrDefault(), options.Ages.FirstOrDefault());
var (generatedVoiceId, audioFilePath) = await ElevenLabsClient.VoiceGenerationEndpoint.GenerateVoiceAsync(generateRequest);
var generateRequest = new GeneratedVoicePreviewRequest("First we thought the PC was a calculator. Then we found out how to turn numbers into letters and we thought it was a typewriter.", options.Genders.FirstOrDefault(), options.Accents.FirstOrDefault(), options.Ages.FirstOrDefault());
var (generatedVoiceId, audioData) = await ElevenLabsClient.VoiceGenerationEndpoint.GenerateVoicePreviewAsync(generateRequest);
Console.WriteLine(generatedVoiceId);
Console.WriteLine(audioFilePath);
Assert.IsFalse(audioData.IsEmpty);
var createVoiceRequest = new CreateVoiceRequest("Test Voice Lab Create Voice", "This is a test voice", generatedVoiceId);
File.Delete(audioFilePath);
Assert.NotNull(createVoiceRequest);
var result = await ElevenLabsClient.VoiceGenerationEndpoint.CreateVoiceAsync(createVoiceRequest);
Assert.NotNull(result);
Console.WriteLine(result.Id);
var deleteResult = await ElevenLabsClient.VoicesEndpoint.DeleteVoiceAsync(result.Id);
Assert.NotNull(deleteResult);
Assert.IsTrue(deleteResult);
}
}
Expand Down
23 changes: 21 additions & 2 deletions ElevenLabs-DotNet/BaseEndPoint.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System.Collections.Generic;
using System.Linq;

namespace ElevenLabs
{
public abstract class BaseEndPoint
Expand All @@ -17,7 +20,23 @@ public abstract class BaseEndPoint
/// Gets the full formatted url for the API endpoint.
/// </summary>
/// <param name="endpoint">The endpoint url.</param>
protected string GetUrl(string endpoint = "")
=> string.Format(Api.ElevenLabsClientSettings.BaseRequestUrlFormat, $"{Root}{endpoint}");
/// <param name="queryParameters">Optional, parameters to add to the endpoint.</param>
protected string GetUrl(string endpoint = "", Dictionary<string, string> queryParameters = null)
{
var result = string.Format(Api.ElevenLabsClientSettings.BaseRequestUrlFormat, $"{Root}{endpoint}");

if (queryParameters is { Count: not 0 })
{
result += $"?{string.Join("&", queryParameters.Select(parameter => $"{parameter.Key}={parameter.Value}"))}";
}

return result;
}

/// <summary>
/// Enables or disables the logging of all http responses of header and body information for this endpoint.<br/>
/// WARNING! Enabling this in your production build, could potentially leak sensitive information!
/// </summary>
public bool EnableDebug { get; set; }
}
}
38 changes: 36 additions & 2 deletions ElevenLabs-DotNet/ElevenLabs-DotNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,42 @@
<Company>RageAgainstThePixel</Company>
<Copyright>2023</Copyright>
<PackageTags>ElevenLabs, AI, ML, Voice, TTS</PackageTags>
<Version>1.3.6</Version>
<PackageReleaseNotes>Version 1.3.6
<Version>2.0.0</Version>
<PackageReleaseNotes>Version 2.0.0
Refactoring to support latest API changes
- Biggest Change is the new VoiceClip signature for all endpoints which contains all the information about the generated clip.
- Refactored HistoryEndpoint
- Made HistoryInfo public
- GetHistoryAsync now returns HistoryInfo and contains additional pageSize and startAfter properties
- Added GetHistoryItemAsync
- Renamed GetHistoryAudioAsync -&gt; DownloadHistoryAudioAsync
- DownloadHistoryItemsAsync now returns a list of VoiceClips, and no longer asks for saveDirectory
- HistoryItem.TextHash was modified to generate hash based on item id, instead of voiceId
- Refactored ModelsEndpoint
- Added Model.MultiLingualV2
- Refactored TextToSpeechEndpoint
- Refactored TextToSpeechAsync
- Removed saveDirectory parameter
- Removed deleteCachedFile parameter
- Added outputFormat parameter
- Changed return type to VoiceClip
- Refactored StreamTextToSpeechAsync
- Removed saveDirectory parameter
- Removed deleteCachedFile parameter
- Added outputFormat
- Added partialClipCallback for queuing and playing partial clips as they are received
- Refactored VoiceGenerationEndpoint
- Renamed GenerateVoiceAsync -&gt; GenerateVoicePreviewAsync
- Removed saveDirectory parameter
- Renamed GeneratedVoiceRequest -&gt; GeneratedVoicePreviewRequest
- Refactored VoicesEndpoint
- Preserve default values in VoiceSettings
- Refactored GetVoiceAsync
- withSettings parameter is now false by default per API
- Renamed GetVoiceSampleAsync -&gt; DownloadVoiceSampleAsync
- Changed return type to VoiceClip
- Removed saveDirectory parameter
Version 1.3.6
- Added Voice.HighQualityBaseModelIds
- Added CreateVoiceRequest.Description
Version 1.3.5
Expand Down
10 changes: 4 additions & 6 deletions ElevenLabs-DotNet/ElevenLabsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,6 @@ public ElevenLabsClient(ElevenLabsAuthentication elevenLabsAuthentication = null
Client.DefaultRequestHeaders.Add("User-Agent", "ElevenLabs-DotNet");
Client.DefaultRequestHeaders.Add("xi-api-key", ElevenLabsAuthentication.ApiKey);

JsonSerializationOptions = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

UserEndpoint = new UserEndpoint(this);
VoicesEndpoint = new VoicesEndpoint(this);
ModelsEndpoint = new ModelsEndpoint(this);
Expand All @@ -59,7 +54,10 @@ public ElevenLabsClient(ElevenLabsAuthentication elevenLabsAuthentication = null
/// <summary>
/// The <see cref="JsonSerializationOptions"/> to use when making calls to the API.
/// </summary>
internal JsonSerializerOptions JsonSerializationOptions { get; }
internal static JsonSerializerOptions JsonSerializationOptions { get; } = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

/// <summary>
/// The API authentication information to use for API calls
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal static class HttpResponseMessageExtensions
{
public static async Task<string> ReadAsStringAsync(this HttpResponseMessage response, bool debugResponse = false, [CallerMemberName] string methodName = null)
{
var responseAsString = await response.Content.ReadAsStringAsync();
var responseAsString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

if (!response.IsSuccessStatusCode)
{
Expand Down
31 changes: 1 addition & 30 deletions ElevenLabs-DotNet/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
Expand All @@ -17,7 +16,7 @@ internal static class StringExtensions
/// <returns>A new <see cref="Guid"/> that represents the string.</returns>
public static Guid GenerateGuid(this string @string)
{
using MD5 md5 = MD5.Create();
using var md5 = MD5.Create();
return new Guid(md5.ComputeHash(Encoding.Default.GetBytes(@string)));
}

Expand All @@ -26,33 +25,5 @@ public static Guid GenerateGuid(this string @string)
/// </summary>
public static StringContent ToJsonStringContent(this string json)
=> new StringContent(json, Encoding.UTF8, "application/json");

/// <summary>
/// Create a new directory based on the current string format.
/// </summary>
/// <param name="parentDirectory"></param>
/// <param name="newDirectoryName"></param>
/// <returns>Full path to the newly created directory.</returns>
public static string CreateNewDirectory(this string parentDirectory, string newDirectoryName)
{
if (string.IsNullOrWhiteSpace(parentDirectory))
{
throw new ArgumentNullException(nameof(parentDirectory));
}

if (string.IsNullOrWhiteSpace(newDirectoryName))
{
throw new ArgumentNullException(nameof(newDirectoryName));
}

var voiceDirectory = Path.Combine(parentDirectory, newDirectoryName);

if (!Directory.Exists(voiceDirectory))
{
Directory.CreateDirectory(voiceDirectory);
}

return voiceDirectory;
}
}
}
Loading

0 comments on commit 38cd9a7

Please sign in to comment.