diff --git a/.fernignore b/.fernignore index 403ed2a..e209c63 100644 --- a/.fernignore +++ b/.fernignore @@ -3,17 +3,15 @@ README.md LICENSE.md src/AssemblyAI/AssemblyAI.csproj -src/AssemblyAI/AssemblyAIClient.cs src/AssemblyAI/ClientOptions.cs +src/AssemblyAI/Constants.cs +src/AssemblyAI/UserAgent.cs +src/AssemblyAI/AssemblyAIClient.cs src/AssemblyAI/DependencyInjectionExtensions.cs +src/AssemblyAI/Files/FilesCustomClient.cs +src/AssemblyAI/Transcripts/TranscriptsCustomClient.cs src/AssemblyAI/Realtime/RealtimeTranscriber.cs src/AssemblyAI/Realtime/WebSocketClient -src/AssemblyAI/Constants.cs -src/AssemblyAI/UserAgent.cs src/AssemblyAI.Test Samples -samples - - - diff --git a/.gitignore b/.gitignore index b22978f..497377b 100644 --- a/.gitignore +++ b/.gitignore @@ -477,4 +477,4 @@ $RECYCLE.BIN/ *.lnk .idea -.runsettings \ No newline at end of file +.runsettings diff --git a/src/AssemblyAI.Test/AssemblyAI.Test.csproj b/src/AssemblyAI.Test/AssemblyAI.Test.csproj index 9e84e9d..c9b6ab5 100644 --- a/src/AssemblyAI.Test/AssemblyAI.Test.csproj +++ b/src/AssemblyAI.Test/AssemblyAI.Test.csproj @@ -23,4 +23,10 @@ + + + PreserveNewest + + + \ No newline at end of file diff --git a/src/AssemblyAI.Test/FilesClientTests.cs b/src/AssemblyAI.Test/FilesClientTests.cs new file mode 100644 index 0000000..2712cdb --- /dev/null +++ b/src/AssemblyAI.Test/FilesClientTests.cs @@ -0,0 +1,49 @@ +using NUnit.Framework; + +namespace AssemblyAI.Test; + +[TestFixture] +public class FilesClientTests +{ + private string _apiKey; + + [SetUp] + public void Setup() + { + // Retrieve the API key from the .runsettings file + _apiKey = TestContext.Parameters.Get("ASSEMBLYAI_API_KEY"); + if(string.IsNullOrEmpty(_apiKey)) throw new Exception("ASSEMBLYAI_API_KEY .runsetting parameter is not set."); + } + + [Test] + public async Task Should_Upload_File_Using_FileInfo() + { + // Assuming there's a method to create a configured RawClient instance + var client = new AssemblyAIClient(_apiKey); + + // Adjust the path to where your test file is located + var testFilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "nbc.mp3"); + var fileInfo = new FileInfo(testFilePath); + + var uploadedFile = await client.Files.UploadAsync(fileInfo).ConfigureAwait(false); + + Assert.That(uploadedFile, Is.Not.Null); + Assert.That(uploadedFile.UploadUrl, Is.Not.Null); + } + + [Test] + public async Task Should_Upload_File_Using_Stream() + { + // Assuming there's a method to create a configured RawClient instance + var client = new AssemblyAIClient(_apiKey); + + // Adjust the path to where your test file is located + var testFilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "nbc.mp3"); + await using var fileStream = File.OpenRead(testFilePath); + + var uploadedFile = await client.Files.UploadAsync(fileStream).ConfigureAwait(false); + + Assert.That(uploadedFile, Is.Not.Null); + Assert.That(uploadedFile.UploadUrl, Is.Not.Null); + } +} \ No newline at end of file diff --git a/src/AssemblyAI.Test/TestData/nbc.mp3 b/src/AssemblyAI.Test/TestData/nbc.mp3 new file mode 100644 index 0000000..50ff56f Binary files /dev/null and b/src/AssemblyAI.Test/TestData/nbc.mp3 differ diff --git a/src/AssemblyAI.Test/TranscriptTests.cs b/src/AssemblyAI.Test/TranscriptTests.cs deleted file mode 100644 index 394c96c..0000000 --- a/src/AssemblyAI.Test/TranscriptTests.cs +++ /dev/null @@ -1,19 +0,0 @@ -using NUnit.Framework; - -namespace AssemblyAI.Test; - -[TestFixture] -public class TranscriptTests -{ - [Test] - public async Task TestTranscript() - { - var client = new AssemblyAIClient(""); - var transcript = await client.Transcripts.SubmitAsync(new TranscriptParams - { - AudioUrl = "https://storage.googleapis.com/aai-docs-samples/nbc.mp3" - }); - Assert.That(transcript, Is.Not.Null); - Assert.That(transcript.Status, Is.EqualTo(TranscriptStatus.Queued)); - } -} \ No newline at end of file diff --git a/src/AssemblyAI.Test/TranscriptsClientTests.cs b/src/AssemblyAI.Test/TranscriptsClientTests.cs new file mode 100644 index 0000000..ce129e0 --- /dev/null +++ b/src/AssemblyAI.Test/TranscriptsClientTests.cs @@ -0,0 +1,149 @@ +using NUnit.Framework; + +namespace AssemblyAI.Test; + +[TestFixture] +public class TranscriptsClientTests +{ + private string _apiKey; + + [SetUp] + public void Setup() + { + // Retrieve the API key from the .runsettings file + _apiKey = TestContext.Parameters.Get("ASSEMBLYAI_API_KEY"); + if(string.IsNullOrEmpty(_apiKey)) throw new Exception("ASSEMBLYAI_API_KEY .runsettings parameter is not set."); + } + + [Test] + public async Task Should_Submit_Using_Uri() + { + // Assuming there's a method to create a configured RawClient instance + var client = new AssemblyAIClient(_apiKey); + + var transcript = await client.Transcripts.SubmitAsync( + new Uri("https://storage.googleapis.com/aai-docs-samples/nbc.mp3") + ).ConfigureAwait(false); + + Assert.That(transcript, Is.Not.Null); + Assert.That(transcript.Id, Is.Not.Null); + Assert.That(transcript.Status, Is.EqualTo(TranscriptStatus.Queued)); + } + + [Test] + public async Task Should_Submit_Using_Stream() + { + // Assuming there's a method to create a configured RawClient instance + var client = new AssemblyAIClient(_apiKey); + + // Adjust the path to where your test file is located + var testFilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "nbc.mp3"); + await using var stream = File.OpenRead(testFilePath); + + var transcript = await client.Transcripts.SubmitAsync(stream).ConfigureAwait(false); + + Assert.That(transcript, Is.Not.Null); + Assert.That(transcript.Id, Is.Not.Null); + Assert.That(transcript.Status, Is.EqualTo(TranscriptStatus.Queued)); + } + + [Test] + public async Task Should_Submit_Using_FileInfo() + { + // Assuming there's a method to create a configured RawClient instance + var client = new AssemblyAIClient(_apiKey); + + // Adjust the path to where your test file is located + var testFilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "nbc.mp3"); + var fileInfo = new FileInfo(testFilePath); + + var transcript = await client.Transcripts.SubmitAsync(fileInfo).ConfigureAwait(false); + + Assert.That(transcript, Is.Not.Null); + Assert.That(transcript.Id, Is.Not.Null); + Assert.That(transcript.Status, Is.EqualTo(TranscriptStatus.Queued)); + } + + [Test] + public async Task Should_Transcribe_Using_Uri() + { + // Assuming there's a method to create a configured RawClient instance + var client = new AssemblyAIClient(_apiKey); + + var transcript = await client.Transcripts.TranscribeAsync( + new Uri("https://storage.googleapis.com/aai-docs-samples/nbc.mp3") + ).ConfigureAwait(false); + + Assert.That(transcript, Is.Not.Null); + Assert.That(transcript.Id, Is.Not.Null); + Assert.That(transcript.Status, Is.EqualTo(TranscriptStatus.Queued)); + } + + [Test] + public async Task Should_Transcribe_From_FileInfo() + { + // Assuming there's a method to create a configured RawClient instance + var client = new AssemblyAIClient(_apiKey); + + // Adjust the path to where your test file is located + var testFilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "nbc.mp3"); + var fileInfo = new FileInfo(testFilePath); + + var transcript = await client.Transcripts.TranscribeAsync(fileInfo).ConfigureAwait(false); + + Assert.That(transcript, Is.Not.Null); + Assert.That(transcript.Id, Is.Not.Null); + Assert.That(transcript.Status, Is.EqualTo(TranscriptStatus.Completed)); + } + + [Test] + public async Task Should_Transcribe_Using_Stream() + { + // Assuming there's a method to create a configured RawClient instance + var client = new AssemblyAIClient(_apiKey); + + // Adjust the path to where your test file is located + var testFilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "nbc.mp3"); + await using var stream = File.OpenRead(testFilePath); + + var transcript = await client.Transcripts.TranscribeAsync(stream).ConfigureAwait(false); + + Assert.That(transcript, Is.Not.Null); + Assert.That(transcript.Id, Is.Not.Null); + Assert.That(transcript.Status, Is.EqualTo(TranscriptStatus.Completed)); + } + + [Test] + public async Task Should_Wait_Until_Ready() + { + // Assuming there's a method to create a configured RawClient instance + var client = new AssemblyAIClient(_apiKey); + + // Adjust the path to where your test file is located + var testFilePath = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "nbc.mp3"); + await using var stream = File.OpenRead(testFilePath); + + var transcript = await client.Transcripts.SubmitAsync(stream).ConfigureAwait(false); + transcript = await client.Transcripts.WaitUntilReady(transcript.Id).ConfigureAwait(false); + + Assert.That(transcript, Is.Not.Null); + Assert.That(transcript.Id, Is.Not.Null); + Assert.That(transcript.Status, Is.EqualTo(TranscriptStatus.Completed)); + } + + [Test] + public async Task Should_Paginate_Transcripts() + { + // Assuming there's a method to create a configured RawClient instance + var client = new AssemblyAIClient(_apiKey); + var transcriptPage = await client.Transcripts.ListAsync().ConfigureAwait(false); + Assert.That(transcriptPage, Is.Not.Null); + Assert.That(transcriptPage.PageDetails.PrevUrl, Is.Not.Null); + Assert.That(transcriptPage.Transcripts, Is.Not.Empty); + + var prevPage = await client.Transcripts.ListAsync(transcriptPage.PageDetails.PrevUrl); + Assert.That(transcriptPage, Is.Not.Null); + Assert.That(transcriptPage.PageDetails.NextUrl, Is.Not.Null); + Assert.That(transcriptPage.Transcripts, Is.Not.Empty); + } +} \ No newline at end of file diff --git a/src/AssemblyAI.Test/sample.runsettings b/src/AssemblyAI.Test/sample.runsettings new file mode 100644 index 0000000..eade2b2 --- /dev/null +++ b/src/AssemblyAI.Test/sample.runsettings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/AssemblyAI/AssemblyAIClient.cs b/src/AssemblyAI/AssemblyAIClient.cs index 9c73a42..987f2f0 100644 --- a/src/AssemblyAI/AssemblyAIClient.cs +++ b/src/AssemblyAI/AssemblyAIClient.cs @@ -1,8 +1,9 @@ +#nullable enable + using System.Net.Http; using AssemblyAI; using AssemblyAI.Core; -#nullable enable namespace AssemblyAI; @@ -38,14 +39,14 @@ public AssemblyAIClient(ClientOptions clientOptions) } Files = new FilesClient(client); - Transcripts = new TranscriptsClient(client); + Transcripts = new ExtendedTranscriptsClient(client, this); Realtime = new RealtimeClient(client); Lemur = new LemurClient(client); } public FilesClient Files { get; init; } - public TranscriptsClient Transcripts { get; init; } + public ExtendedTranscriptsClient Transcripts { get; init; } public RealtimeClient Realtime { get; init; } diff --git a/src/AssemblyAI/Files/FilesClient.cs b/src/AssemblyAI/Files/FilesClient.cs index 4f51ec5..cb73331 100644 --- a/src/AssemblyAI/Files/FilesClient.cs +++ b/src/AssemblyAI/Files/FilesClient.cs @@ -7,7 +7,7 @@ namespace AssemblyAI; -public class FilesClient +public partial class FilesClient { private RawClient _client; diff --git a/src/AssemblyAI/Files/FilesCustomClient.cs b/src/AssemblyAI/Files/FilesCustomClient.cs new file mode 100644 index 0000000..b453d46 --- /dev/null +++ b/src/AssemblyAI/Files/FilesCustomClient.cs @@ -0,0 +1,20 @@ +using System.Net.Http; +using System.Text.Json; +using AssemblyAI; +using AssemblyAI.Core; + +#nullable enable + +namespace AssemblyAI; + +public partial class FilesClient +{ + /// + /// Upload a media file to AssemblyAI's servers. + /// + public async Task UploadAsync(FileInfo audioFile) + { + using var audioFileStream = audioFile.OpenRead(); + return await UploadAsync(audioFileStream).ConfigureAwait(false); + } +} diff --git a/src/AssemblyAI/Transcripts/ExtendedTranscriptsClient.cs b/src/AssemblyAI/Transcripts/ExtendedTranscriptsClient.cs new file mode 100644 index 0000000..3649017 --- /dev/null +++ b/src/AssemblyAI/Transcripts/ExtendedTranscriptsClient.cs @@ -0,0 +1,134 @@ +#nullable enable + +using AssemblyAI.Core; + +namespace AssemblyAI; + +public class ExtendedTranscriptsClient(RawClient client, AssemblyAIClient assemblyAIClient) : TranscriptsClient(client) +{ + public Task SubmitAsync(FileInfo audioFile) => SubmitAsync(audioFile, new TranscriptOptionalParams()); + + public async Task SubmitAsync(FileInfo audioFile, TranscriptOptionalParams transcriptParams) + { + using var audioFileStream = audioFile.OpenRead(); + return await SubmitAsync(audioFileStream, transcriptParams).ConfigureAwait(false); + } + + public Task SubmitAsync(Stream audioFileStream) => + SubmitAsync(audioFileStream, new TranscriptOptionalParams()); + + public async Task SubmitAsync(Stream audioFileStream, TranscriptOptionalParams transcriptParams) + { + var fileUpload = await assemblyAIClient.Files.UploadAsync(audioFileStream).ConfigureAwait(false); + return await SubmitAsync(new Uri(fileUpload.UploadUrl), transcriptParams).ConfigureAwait(false); + } + + public Task SubmitAsync(Uri audioFileUrl) => SubmitAsync(audioFileUrl, new TranscriptOptionalParams()); + + public async Task SubmitAsync(Uri audioFileUrl, TranscriptOptionalParams transcriptParams) + { + return await SubmitAsync(CreateParams(audioFileUrl, transcriptParams)).ConfigureAwait(false); + } + + public Task TranscribeAsync(FileInfo audioFile) => + TranscribeAsync(audioFile, new TranscriptOptionalParams()); + + public async Task TranscribeAsync(FileInfo audioFile, TranscriptOptionalParams transcriptParams) + { + using var audioFileStream = audioFile.OpenRead(); + return await TranscribeAsync(audioFileStream, transcriptParams).ConfigureAwait(false); + } + + public Task TranscribeAsync(Stream audioFileStream) => + TranscribeAsync(audioFileStream, new TranscriptOptionalParams()); + + public async Task TranscribeAsync(Stream audioFileStream, TranscriptOptionalParams transcriptParams) + { + var fileUpload = await assemblyAIClient.Files.UploadAsync(audioFileStream).ConfigureAwait(false); + return await TranscribeAsync(new Uri(fileUpload.UploadUrl), transcriptParams).ConfigureAwait(false); + } + + public Task TranscribeAsync(Uri audioFileUrl) => + TranscribeAsync(audioFileUrl, new TranscriptOptionalParams()); + + public async Task TranscribeAsync(Uri audioFileUrl, TranscriptOptionalParams transcriptParams) + { + var transcript = await SubmitAsync(CreateParams(audioFileUrl, transcriptParams)).ConfigureAwait(false); + transcript = await WaitUntilReady(transcript.Id).ConfigureAwait(false); + return transcript; + } + + public async Task WaitUntilReady(string id) + { + var transcript = await GetAsync(id).ConfigureAwait(false); + while (transcript.Status != TranscriptStatus.Completed && transcript.Status != TranscriptStatus.Error) + { + await Task.Delay(1000).ConfigureAwait(false); + transcript = await GetAsync(transcript.Id).ConfigureAwait(false); + } + + return transcript; + } + + private TranscriptParams CreateParams(Uri audioFileUrl, TranscriptOptionalParams transcriptParams) + { + return new TranscriptParams + { + AudioUrl = audioFileUrl.ToString() + // TODO: map other parameters + }; + } + + public Task ListAsync() => ListAsync(new ListTranscriptParams()); + + /// + /// Retrieve a list of transcripts you created. + /// Transcripts are sorted from newest to oldest. The previous URL always points to a page with older transcripts. + /// + public async Task ListAsync(string listUrl) + { + // this would be easier to just call the given URL, + // but the raw client doesn't let us make requests to full URL + // so we'll parse the querystring and pass it to `ListAsync`. + + var queryString = listUrl.Substring(listUrl.IndexOf('?') + 1) + .Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries) + .Select(k => k.Split('=')) + .Where(k => k.Length == 2) + .ToLookup(a => a[0], a => Uri.UnescapeDataString(a[1]) + , StringComparer.OrdinalIgnoreCase); + var listTranscriptParams = new ListTranscriptParams(); + if (queryString.Contains("limit")) + { + listTranscriptParams.Limit = int.Parse(queryString["limit"].First()); + } + + if (queryString.Contains("status")) + { + listTranscriptParams.Status = + (TranscriptStatus)Enum.Parse(typeof(TranscriptStatus), queryString["limit"].First()); + } + + if (queryString.Contains("created_on")) + { + listTranscriptParams.CreatedOn = queryString["created_on"].First(); + } + + if (queryString.Contains("before_id")) + { + listTranscriptParams.BeforeId = queryString["before_id"].First(); + } + + if (queryString.Contains("after_id")) + { + listTranscriptParams.AfterId = queryString["after_id"].First(); + } + + if (queryString.Contains("throttled_only")) + { + listTranscriptParams.ThrottledOnly = bool.Parse(queryString["throttled_only"].First()); + } + + return await ListAsync(listTranscriptParams).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/AssemblyAI/Transcripts/Requests/ListTranscriptParams.cs b/src/AssemblyAI/Transcripts/Requests/ListTranscriptParams.cs index 96f2511..e73946c 100644 --- a/src/AssemblyAI/Transcripts/Requests/ListTranscriptParams.cs +++ b/src/AssemblyAI/Transcripts/Requests/ListTranscriptParams.cs @@ -9,30 +9,30 @@ public record ListTranscriptParams /// /// Maximum amount of transcripts to retrieve /// - public int? Limit { get; init; } + public int? Limit { get; set; } /// /// Filter by transcript status /// - public TranscriptStatus? Status { get; init; } + public TranscriptStatus? Status { get; set; } /// /// Only get transcripts created on this date /// - public string? CreatedOn { get; init; } + public string? CreatedOn { get; set; } /// /// Get transcripts that were created before this transcript ID /// - public string? BeforeId { get; init; } + public string? BeforeId { get; set; } /// /// Get transcripts that were created after this transcript ID /// - public string? AfterId { get; init; } + public string? AfterId { get; set; } /// /// Only get throttled transcripts, overrides the status filter /// - public bool? ThrottledOnly { get; init; } + public bool? ThrottledOnly { get; set; } }