Skip to content

Commit

Permalink
Added possibility to authenticate HF Downloader for model download + …
Browse files Browse the repository at this point in the history
…retrieve token from secrets in github actions
  • Loading branch information
sandrohanea committed Jan 12, 2025
1 parent 34d8ba1 commit 3f2443f
Show file tree
Hide file tree
Showing 20 changed files with 70 additions and 42 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ jobs:
- wasm
- linux
uses: ./.github/workflows/dotnet.yml
secrets: inherit

dotnet-maui-build-and-test:
needs:
Expand All @@ -85,3 +86,4 @@ jobs:
- wasm
- linux
uses: ./.github/workflows/dotnet-maui.yml
secrets: inherit
3 changes: 3 additions & 0 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ permissions:
contents: read
checks: write

env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}

jobs:
dotnet-macos:
runs-on: macos-15
Expand Down
2 changes: 1 addition & 1 deletion Whisper.net.Demo/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ async Task Demo(Options opt)
if (!File.Exists(opt.ModelName))
{
Console.WriteLine($"Downloading Model {opt.ModelName}");
using var modelStream = await WhisperGgmlDownloader.GetGgmlModelAsync(opt.ModelType);
using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(opt.ModelType);
using var fileWriter = File.OpenWrite(opt.ModelName);
await modelStream.CopyToAsync(fileWriter);
}
Expand Down
40 changes: 14 additions & 26 deletions Whisper.net/Ggml/WhisperGgmlDownloader.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// Licensed under the MIT license: https://opensource.org/licenses/MIT

using System.IO.Compression;

namespace Whisper.net.Ggml;

public static class WhisperGgmlDownloader
public class WhisperGgmlDownloader(HttpClient httpClient)
{
private static readonly Lazy<HttpClient> httpClient = new(() => new HttpClient() { Timeout = Timeout.InfiniteTimeSpan });
private static readonly Lazy<WhisperGgmlDownloader> defaultInstance = new
(
() => new WhisperGgmlDownloader(new() { Timeout = TimeSpan.FromHours(1) })
);

public static WhisperGgmlDownloader Default { get; } = defaultInstance.Value;

/// <summary>
/// Gets the download stream for the model
Expand All @@ -15,15 +18,15 @@ public static class WhisperGgmlDownloader
/// <param name="quantization">The quantization of the model.</param>
/// <param name="cancellationToken">A cancellation token used to cancell the request to huggingface.</param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static async Task<Stream> GetGgmlModelAsync(GgmlType type, QuantizationType quantization = QuantizationType.NoQuantization, CancellationToken cancellationToken = default)
public async Task<Stream> GetGgmlModelAsync(GgmlType type, QuantizationType quantization = QuantizationType.NoQuantization, CancellationToken cancellationToken = default)
{
var subdirectory = GetQuantizationSubdirectory(quantization);
var modelName = GetModelName(type);

var url = $"https://huggingface.co/sandrohanea/whisper.net/resolve/v3/{subdirectory}/{modelName}.bin";

var request = new HttpRequestMessage(HttpMethod.Get, url);
var response = await httpClient.Value.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();

#if NETSTANDARD
Expand All @@ -39,12 +42,12 @@ public static async Task<Stream> GetGgmlModelAsync(GgmlType type, QuantizationTy
/// <param name="type">The type of the model which needs to be downloaded.</param>
/// <param name="cancellationToken">A cancellation token used to stop the request to huggingface.</param>
/// <returns></returns>
public static async Task<Stream> GetEncoderOpenVinoModelAsync(GgmlType type, CancellationToken cancellationToken = default)
public async Task<Stream> GetEncoderOpenVinoModelAsync(GgmlType type, CancellationToken cancellationToken = default)
{
var modelName = GetModelName(type);
var url = $"https://huggingface.co/sandrohanea/whisper.net/resolve/v3/openvino/{modelName}-encoder.zip";
var request = new HttpRequestMessage(HttpMethod.Get, url);
var response = await httpClient.Value.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
#if NETSTANDARD
return await response.Content.ReadAsStreamAsync();
Expand All @@ -58,7 +61,7 @@ public static async Task<Stream> GetEncoderOpenVinoModelAsync(GgmlType type, Can
/// </summary>
/// <param name="type"> The type of the model which needs to be loaded</param>
/// <returns></returns>
public static string GetOpenVinoManifestFileName(GgmlType type)
public string GetOpenVinoManifestFileName(GgmlType type)
{
var modelName = GetModelName(type);
return $"{modelName}-encoder.xml";
Expand All @@ -73,13 +76,13 @@ public static string GetOpenVinoManifestFileName(GgmlType type)
/// Needs to be extracted on in the same directory as the ggml model, also ggml model needs to be loaded using file path, not stream.
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static async Task<Stream> GetEncoderCoreMLModelAsync(GgmlType type, CancellationToken cancellationToken = default)
public async Task<Stream> GetEncoderCoreMLModelAsync(GgmlType type, CancellationToken cancellationToken = default)
{
var modelName = GetModelName(type);
var url = $"https://huggingface.co/sandrohanea/whisper.net/resolve/v3/coreml/{modelName}-encoder.zip";

var request = new HttpRequestMessage(HttpMethod.Get, url);
var response = await httpClient.Value.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();

#if NETSTANDARD
Expand All @@ -89,21 +92,6 @@ public static async Task<Stream> GetEncoderCoreMLModelAsync(GgmlType type, Cance
#endif
}

/// <summary>
/// Extracts the given zip stream to the given path.
/// </summary>
/// <param name="zipStream">The zip stream to be extracted.</param>
/// <param name="path">The path.</param>
/// <remarks>
/// In order to work, you'll need to provide the same path as the ggml model.
/// </remarks>
/// <returns></returns>
public static async Task ExtractToPath(this Task<Stream> zipStream, string path)
{
using var zipArchive = new ZipArchive(await zipStream, ZipArchiveMode.Read);
zipArchive.ExtractToDirectory(path);
}

private static string GetModelName(GgmlType type)
{
return type switch
Expand Down
23 changes: 23 additions & 0 deletions Whisper.net/Ggml/ZipStreamExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed under the MIT license: https://opensource.org/licenses/MIT

using System.IO.Compression;

namespace Whisper.net.Ggml;

public static class ZipStreamExtensions
{
/// <summary>
/// Extracts the given zip stream to the given path.
/// </summary>
/// <param name="zipStream">The zip stream to be extracted.</param>
/// <param name="path">The path.</param>
/// <remarks>
/// In order to work, you'll need to provide the same path as the ggml model.
/// </remarks>
/// <returns></returns>
public static async Task ExtractToPath(this Task<Stream> zipStream, string path)
{
using var zipArchive = new ZipArchive(await zipStream, ZipArchiveMode.Read);
zipArchive.ExtractToDirectory(path);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ else
if (whisperFactory == null)
{
using var memoryStream = new MemoryStream();
var model = await WhisperGgmlDownloader.GetGgmlModelAsync(GgmlType.Tiny);
var model = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(GgmlType.Tiny);
await model.CopyToAsync(memoryStream);
whisperFactory = WhisperFactory.FromBuffer(memoryStream.ToArray());
}
Expand Down
2 changes: 1 addition & 1 deletion examples/CoreML/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ await WhisperGgmlDownloader.GetEncoderCoreMLModelAsync(ggmlType)
private static async Task DownloadModel(string fileName, GgmlType ggmlType)
{
Console.WriteLine($"Downloading Model {fileName}");
using var modelStream = await WhisperGgmlDownloader.GetGgmlModelAsync(ggmlType);
using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(ggmlType);
using var fileWriter = File.OpenWrite(fileName);
await modelStream.CopyToAsync(fileWriter);
}
Expand Down
2 changes: 1 addition & 1 deletion examples/Diarization/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
static async Task DownloadModel(string fileName, GgmlType ggmlType)
{
Console.WriteLine($"Downloading Model {fileName}");
using var modelStream = await WhisperGgmlDownloader.GetGgmlModelAsync(ggmlType);
using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(ggmlType);
using var fileWriter = File.OpenWrite(fileName);
await modelStream.CopyToAsync(fileWriter);
}
2 changes: 1 addition & 1 deletion examples/MultiRuntime/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public static async Task Main(string[] args)
private static async Task DownloadModel(string fileName, GgmlType ggmlType)
{
Console.WriteLine($"Downloading Model {fileName}");
using var modelStream = await WhisperGgmlDownloader.GetGgmlModelAsync(ggmlType);
using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(ggmlType);
using var fileWriter = File.OpenWrite(fileName);
await modelStream.CopyToAsync(fileWriter);
}
Expand Down
2 changes: 1 addition & 1 deletion examples/NAudioMp3/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static async Task Main(string[] args)
private static async Task DownloadModel(string fileName, GgmlType ggmlType)
{
Console.WriteLine($"Downloading Model {fileName}");
using var modelStream = await WhisperGgmlDownloader.GetGgmlModelAsync(ggmlType);
using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(ggmlType);
using var fileWriter = File.OpenWrite(fileName);
await modelStream.CopyToAsync(fileWriter);
}
Expand Down
2 changes: 1 addition & 1 deletion examples/NAudioResampleWav/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static async Task Main(string[] args)
private static async Task DownloadModel(string fileName, GgmlType ggmlType)
{
Console.WriteLine($"Downloading Model {fileName}");
using var modelStream = await WhisperGgmlDownloader.GetGgmlModelAsync(ggmlType);
using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(ggmlType);
using var fileWriter = File.OpenWrite(fileName);
await modelStream.CopyToAsync(fileWriter);
}
Expand Down
2 changes: 1 addition & 1 deletion examples/NvidiaCuda/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public static async Task Main(string[] args)
private static async Task DownloadModel(string fileName, GgmlType ggmlType)
{
Console.WriteLine($"Downloading Model {fileName}");
using var modelStream = await WhisperGgmlDownloader.GetGgmlModelAsync(ggmlType);
using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(ggmlType);
using var fileWriter = File.OpenWrite(fileName);
await modelStream.CopyToAsync(fileWriter);
}
Expand Down
2 changes: 1 addition & 1 deletion examples/OpenVinoExample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ await WhisperGgmlDownloader.GetEncoderOpenVinoModelAsync(ggmlType)
private static async Task DownloadModel(string fileName, GgmlType ggmlType)
{
Console.WriteLine($"Downloading Model {fileName}");
using var modelStream = await WhisperGgmlDownloader.GetGgmlModelAsync(ggmlType);
using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(ggmlType);
using var fileWriter = File.OpenWrite(fileName);
await modelStream.CopyToAsync(fileWriter);
}
Expand Down
2 changes: 1 addition & 1 deletion examples/ParallelExecution/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public static async Task RunInParallel(string name, string wavFileName, WhisperF
private static async Task DownloadModel(string fileName, GgmlType ggmlType)
{
Console.WriteLine($"Downloading Model {fileName}");
using var modelStream = await WhisperGgmlDownloader.GetGgmlModelAsync(ggmlType);
using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(ggmlType);
using var fileWriter = File.OpenWrite(fileName);
await modelStream.CopyToAsync(fileWriter);
}
Expand Down
2 changes: 1 addition & 1 deletion examples/Simple/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public static async Task Main(string[] args)
private static async Task DownloadModel(string fileName, GgmlType ggmlType)
{
Console.WriteLine($"Downloading Model {fileName}");
using var modelStream = await WhisperGgmlDownloader.GetGgmlModelAsync(ggmlType);
using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(ggmlType);
using var fileWriter = File.OpenWrite(fileName);
await modelStream.CopyToAsync(fileWriter);
}
Expand Down
2 changes: 1 addition & 1 deletion examples/SimpleSync/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public static async Task Main(string[] args)
private static async Task DownloadModel(string fileName, GgmlType ggmlType)
{
Console.WriteLine($"Downloading Model {fileName}");
using var modelStream = await WhisperGgmlDownloader.GetGgmlModelAsync(ggmlType);
using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(ggmlType);
using var fileWriter = File.OpenWrite(fileName);
await modelStream.CopyToAsync(fileWriter);
}
Expand Down
2 changes: 1 addition & 1 deletion examples/Vulkan/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public static async Task Main(string[] args)
private static async Task DownloadModel(string fileName, GgmlType ggmlType)
{
Console.WriteLine($"Downloading Model {fileName}");
using var modelStream = await WhisperGgmlDownloader.GetGgmlModelAsync(ggmlType);
using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(ggmlType);
using var fileWriter = File.OpenWrite(fileName);
await modelStream.CopyToAsync(fileWriter);
}
Expand Down
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ For easier integration, Whisper.net provides a Downloader using [Hugging Face](h
var modelName = "ggml-base.bin";
if (!File.Exists(modelName))
{
using var modelStream = await WhisperGgmlDownloader.GetGgmlModelAsync(GgmlType.Base);
using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(GgmlType.Base);
using var fileWriter = File.OpenWrite(modelName);
await modelStream.CopyToAsync(fileWriter);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/Whisper.net.Maui.Tests/MainPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ private async void ContentPage_Loaded(object sender, EventArgs e)
try
{
using var memoryStream = new MemoryStream();
using var modelStream = await WhisperGgmlDownloader.GetGgmlModelAsync(GgmlType.Tiny, QuantizationType.Q4_0);
using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(GgmlType.Tiny, QuantizationType.Q4_0);
await modelStream.CopyToAsync(memoryStream);
using var mauiStream = await FileSystem.OpenAppPackageFileAsync("kennedy.wav");
var audioFileStream = new MemoryStream();
Expand Down
14 changes: 13 additions & 1 deletion tests/Whisper.net.Tests/ModelFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,20 @@ public Task DisposeAsync()

private static async Task<string> DownloadModelAsync(GgmlType type, QuantizationType quantizationType = QuantizationType.NoQuantization)
{
var huggingFaceToken = Environment.GetEnvironmentVariable("HF_TOKEN");
var ggmlModelPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.bin");
var model = await WhisperGgmlDownloader.GetGgmlModelAsync(type, quantizationType);
var downloader = string.IsNullOrEmpty(huggingFaceToken)
? WhisperGgmlDownloader.Default
: new(
new()
{
DefaultRequestHeaders =
{
{ "Authorization", $"Bearer {huggingFaceToken}" }
},
Timeout = TimeSpan.FromHours(1)
});
var model = await downloader.GetGgmlModelAsync(type, quantizationType);
using var fileWriter = File.OpenWrite(ggmlModelPath);
await model.CopyToAsync(fileWriter);
return ggmlModelPath;
Expand Down

0 comments on commit 3f2443f

Please sign in to comment.