diff --git a/Configurations/XAiApiConfiguration.cs b/Configurations/XAiApiConfiguration.cs
index 32a2418..2153bc2 100644
--- a/Configurations/XAiApiConfiguration.cs
+++ b/Configurations/XAiApiConfiguration.cs
@@ -19,5 +19,10 @@ namespace xAiApi.Configurations
/// OCR Configuration ...
///
public XAiOCRDescriptor OCR { get; set; }
+
+ ///
+ /// Transcript Configuration ...
+ ///
+ public XAiTransciptDescriptor Transcript { get; set; }
}
}
\ No newline at end of file
diff --git a/Models/XAiTransciptDescriptor.cs b/Models/XAiTransciptDescriptor.cs
new file mode 100644
index 0000000..7a54bee
--- /dev/null
+++ b/Models/XAiTransciptDescriptor.cs
@@ -0,0 +1,8 @@
+namespace xAiApi.Models
+{
+ public class XAiTransciptDescriptor
+ {
+ public string Language { get; set; }
+ public string ModelsPath { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Providers/Extractors/XAudioFileContentExtractor.cs b/Providers/Extractors/XAudioFileContentExtractor.cs
index 02aad55..8f6e18a 100644
--- a/Providers/Extractors/XAudioFileContentExtractor.cs
+++ b/Providers/Extractors/XAudioFileContentExtractor.cs
@@ -2,11 +2,15 @@ using System;
using System.IO;
using System.Linq;
using Whisper.net;
+using NAudio.Wave;
using System.Threading;
using xAiModels.Models;
+using xCommons.Extensions;
+using xAiApi.Configurations;
+using xExceptions.Constants;
using System.Threading.Tasks;
+using NAudio.MediaFoundation;
using xAiApi.Interfaces.Extractors;
-using DocumentFormat.OpenXml.EMMA;
namespace xAiApi.Providers.Extractors
{
@@ -24,29 +28,30 @@ namespace xAiApi.Providers.Extractors
"audio/mp3",
"audio/wav",
"audio/wave",
+ "audio/x-wav",
"audio/ogg",
"audio/m4a",
"audio/mp4",
+ "audio/aac",
+ "audio/x-m4a",
"audio/webm"
];
- private readonly string language;
- private readonly string whisperModelPath;
-
- public XAudioFileContentExtractor() : this(
- language: "fa",
- whisperModelPath: "Models/ggml-base.bin"
- )
- { }
+ private readonly XAiApiConfiguration configuration;
public XAudioFileContentExtractor(
- string whisperModelPath = "Models/ggml-base.bin",
- string language = "fa"
+ XAiApiConfiguration configuration
)
{
//
- this.language = language;
- this.whisperModelPath = whisperModelPath;
+ if (configuration.Transcript.IsNullOrDefault() ||
+ configuration.Transcript.ModelsPath.IsNullOrEmpty())
+ {
+ XException.InvalidConfiguration.Throw();
+ }
+
+ //
+ this.configuration = configuration;
}
///
@@ -113,14 +118,15 @@ namespace xAiApi.Providers.Extractors
//
using var wavStream = await ConvertToWavAsync(
- memoryStream,
- cancellationToken
+ mimeType: mimeType,
+ inputStream: memoryStream,
+ cancellationToken: cancellationToken
);
//
- using var factory = WhisperFactory.FromPath(whisperModelPath);
+ using var factory = WhisperFactory.FromPath(configuration.Transcript.ModelsPath);
using var processor = factory.CreateBuilder()
- .WithLanguage(language)
+ .WithLanguage(configuration.Transcript.Language)
.Build();
//
@@ -143,17 +149,186 @@ namespace xAiApi.Providers.Extractors
return result;
}
+ //
+ #region Private ...
///
- /// Audio Format Converting ...
+ /// Audio Format Converting to WAV 16kHz Mono 16-bit PCM ...
///
private async Task ConvertToWavAsync(
Stream inputStream,
- CancellationToken cancellationToken
+ string mimeType,
+ CancellationToken cancellationToken = default
)
{
//
- // TODO: Complete this ...
- return await Task.FromResult(inputStream);
+ var result = await Task.Run(() =>
+ {
+ //
+ var memoryStream = new MemoryStream();
+ inputStream.CopyTo(memoryStream);
+ memoryStream.Position = 0;
+
+ //
+ var targetFormat = new WaveFormat(16000, 16, 1);
+ string tempFilePath = null;
+
+ //
+ try
+ {
+ //
+ WaveStream reader = GetAudioReader(memoryStream, mimeType, ref tempFilePath);
+ if (reader == null)
+ {
+ //
+ XException.NotAllowed.Throw(
+ $"Unsupported audio format: {mimeType}"
+ );
+ }
+
+ //
+ var needsConversion =
+ reader.WaveFormat.SampleRate != 16000 ||
+ reader.WaveFormat.Channels != 1 ||
+ reader.WaveFormat.BitsPerSample != 16 ||
+ reader.WaveFormat.Encoding != WaveFormatEncoding.Pcm;
+ var outputStream = new MemoryStream();
+ if (!needsConversion)
+ {
+ //
+ reader.CopyTo(outputStream);
+ reader.Dispose();
+ }
+ else
+ {
+ //
+ MediaFoundationApi.Startup();
+ using var resampler = new MediaFoundationResampler(
+ reader,
+ targetFormat
+ );
+ resampler.ResamplerQuality = 60;
+
+ //
+ WaveFileWriter.WriteWavFileToStream(outputStream, resampler);
+ reader.Dispose();
+ }
+
+ //
+ outputStream.Position = 0;
+ return outputStream;
+ }
+ finally
+ {
+ //
+ if (!string.IsNullOrEmpty(tempFilePath) &&
+ File.Exists(tempFilePath))
+ {
+ //
+ try
+ {
+ File.Delete(tempFilePath);
+ }
+ catch { }
+ }
+
+ //
+ memoryStream.Dispose();
+ }
+ }, cancellationToken);
+
+ //
+ return result;
}
+
+ ///
+ /// Get appropriate WaveStream reader based on MIME type ...
+ ///
+ /// Input stream (MemoryStream)
+ /// MIME type of audio file
+ /// Path to temp file (if created)
+ /// WaveStream reader or null if unsupported
+ private WaveStream GetAudioReader(
+ MemoryStream stream,
+ string mimeType,
+ ref string tempFilePath
+ )
+ {
+ //
+ try
+ {
+ //
+ var normalizedMime = mimeType?.ToLowerInvariant() ?? string.Empty;
+ switch (normalizedMime)
+ {
+ //
+ case "audio/wav":
+ case "audio/wave":
+ case "audio/x-wav":
+ return new WaveFileReader(stream);
+
+ //
+ case "audio/mpeg":
+ case "audio/mp3":
+ return new Mp3FileReader(stream);
+
+ //
+ case "audio/m4a":
+ case "audio/mp4":
+ case "audio/aac":
+ case "audio/ogg":
+ case "audio/webm":
+ case "audio/x-m4a":
+ //
+ var extension = normalizedMime switch
+ {
+ "audio/m4a" or "audio/mp4" or "audio/aac" or "audio/x-m4a" => ".m4a",
+ "audio/ogg" => ".ogg",
+ "audio/webm" => ".webm",
+ _ => ".tmp"
+ };
+ tempFilePath = Path.Combine(
+ Path.GetTempPath(),
+ $"audio_{Guid.NewGuid()}{extension}"
+ );
+
+ //
+ stream.Position = 0;
+ using (var fileStream = File.Create(tempFilePath))
+ {
+ stream.CopyTo(fileStream);
+ }
+
+ //
+ MediaFoundationApi.Startup();
+ return new MediaFoundationReader(tempFilePath);
+
+ default:
+ //
+ tempFilePath = Path.Combine(
+ Path.GetTempPath(),
+ $"audio_{Guid.NewGuid()}.tmp"
+ );
+ stream.Position = 0;
+ using (var fileStream = File.Create(tempFilePath))
+ {
+ stream.CopyTo(fileStream);
+ }
+ try
+ {
+ MediaFoundationApi.Startup();
+ return new MediaFoundationReader(tempFilePath);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ }
+ #endregion
}
}
\ No newline at end of file
diff --git a/appsettings.Development.json b/appsettings.Development.json
index cfd8ad5..ea92863 100644
--- a/appsettings.Development.json
+++ b/appsettings.Development.json
@@ -84,6 +84,10 @@
"EngineMode": "LstmOnly",
"EnableOcrFallback": true,
"DefaultLanguage": "fas+eng"
+ },
+ "Transcript": {
+ "Language": "fa",
+ "ModelsPath": "Models/ggml-base.bin"
}
},
"Logging": {
diff --git a/xAiApi.csproj b/xAiApi.csproj
index 2e72bf9..71e53bb 100644
--- a/xAiApi.csproj
+++ b/xAiApi.csproj
@@ -27,13 +27,6 @@
-
-
-
@@ -53,22 +46,22 @@
-
-
-
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+