-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSetupManager.cs
More file actions
160 lines (136 loc) · 6.01 KB
/
Copy pathSetupManager.cs
File metadata and controls
160 lines (136 loc) · 6.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
using System.Diagnostics;
using System.IO.Compression;
using System.Net.Http;
namespace WhisperTranscriber;
public class SetupManager
{
private const string PythonVersion = "3.11.9";
private static readonly string PythonZipUrl =
$"https://www.python.org/ftp/python/{PythonVersion}/python-{PythonVersion}-embed-amd64.zip";
private const string GetPipUrl = "https://bootstrap.pypa.io/get-pip.py";
private readonly string _baseDir;
public string PythonDir => Path.Combine(_baseDir, "python");
public string PythonExe => Path.Combine(PythonDir, "python.exe");
public string ModelsDir => Path.Combine(_baseDir, "models");
public event Action<string, int>? Progress;
public SetupManager(string baseDir)
{
_baseDir = baseDir;
Directory.CreateDirectory(_baseDir);
Directory.CreateDirectory(ModelsDir);
}
private string BaseMarker => Path.Combine(_baseDir, ".setup_complete");
private string DiarizeMarker => Path.Combine(_baseDir, ".setup_diarize");
public bool IsReady() => File.Exists(PythonExe) && File.Exists(BaseMarker);
public bool IsDiarizeReady() => File.Exists(DiarizeMarker);
public async Task EnsureInstalledAsync(bool installCuda, bool installDiarize, CancellationToken ct = default)
{
if (!File.Exists(PythonExe))
{
await DownloadPythonAsync(ct);
}
if (!IsReady())
{
EnableSitePackages();
await InstallPipAsync(ct);
await InstallPackageAsync("faster-whisper", ct, weight: 50);
if (installCuda)
{
await InstallPackageAsync("nvidia-cublas-cu12", ct, weight: 15);
await InstallPackageAsync("nvidia-cudnn-cu12", ct, weight: 15);
}
File.WriteAllText(BaseMarker, DateTime.UtcNow.ToString("o"));
}
if (installDiarize && !IsDiarizeReady())
{
Report("Installazione pyannote.audio (questo richiede qualche minuto)...", -1);
await InstallPackageAsync("torch", ct, weight: 30);
await InstallPackageAsync("torchaudio", ct, weight: 15);
await InstallPackageAsync("pyannote.audio", ct, weight: 30);
File.WriteAllText(DiarizeMarker, DateTime.UtcNow.ToString("o"));
}
Report("Ambiente pronto", 100);
}
private async Task DownloadPythonAsync(CancellationToken ct)
{
Report("Download Python embedded...", 5);
Directory.CreateDirectory(PythonDir);
var zipPath = Path.Combine(_baseDir, "python.zip");
using (var http = new HttpClient { Timeout = TimeSpan.FromMinutes(5) })
using (var response = await http.GetAsync(PythonZipUrl, HttpCompletionOption.ResponseHeadersRead, ct))
{
response.EnsureSuccessStatusCode();
var total = response.Content.Headers.ContentLength ?? 30_000_000;
await using var src = await response.Content.ReadAsStreamAsync(ct);
await using var dst = File.Create(zipPath);
var buf = new byte[81920];
long readTotal = 0;
int n;
while ((n = await src.ReadAsync(buf, ct)) > 0)
{
await dst.WriteAsync(buf.AsMemory(0, n), ct);
readTotal += n;
var pct = (int)(5 + readTotal * 15.0 / total);
Report($"Download Python: {readTotal / 1024 / 1024} / {total / 1024 / 1024} MB", pct);
}
}
Report("Estrazione Python...", 22);
ZipFile.ExtractToDirectory(zipPath, PythonDir, overwriteFiles: true);
File.Delete(zipPath);
}
private void EnableSitePackages()
{
var pthFile = Directory.GetFiles(PythonDir, "python*._pth").FirstOrDefault();
if (pthFile is null) return;
var content = File.ReadAllText(pthFile);
if (content.Contains("#import site"))
{
content = content.Replace("#import site", "import site");
File.WriteAllText(pthFile, content);
}
}
private async Task InstallPipAsync(CancellationToken ct)
{
var getPipPath = Path.Combine(_baseDir, "get-pip.py");
if (!File.Exists(Path.Combine(PythonDir, "Scripts", "pip.exe")))
{
Report("Download get-pip.py...", 25);
using var http = new HttpClient { Timeout = TimeSpan.FromMinutes(2) };
var bytes = await http.GetByteArrayAsync(GetPipUrl, ct);
await File.WriteAllBytesAsync(getPipPath, bytes, ct);
Report("Installazione pip...", 30);
await RunPythonAsync(new[] { getPipPath, "--no-warn-script-location" }, ct);
File.Delete(getPipPath);
}
}
private async Task InstallPackageAsync(string package, CancellationToken ct, int weight)
{
Report($"Installazione {package}...", -1);
await RunPythonAsync(
new[] { "-m", "pip", "install", "--no-warn-script-location", "--upgrade", package },
ct,
line => Report($"{package}: {line}", -1));
}
private async Task RunPythonAsync(string[] args, CancellationToken ct, Action<string>? onLine = null)
{
var psi = new ProcessStartInfo
{
FileName = PythonExe,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
foreach (var a in args) psi.ArgumentList.Add(a);
using var proc = new Process { StartInfo = psi };
proc.OutputDataReceived += (_, e) => { if (e.Data is not null) onLine?.Invoke(e.Data); };
proc.ErrorDataReceived += (_, e) => { if (e.Data is not null) onLine?.Invoke(e.Data); };
proc.Start();
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
await proc.WaitForExitAsync(ct);
if (proc.ExitCode != 0)
throw new InvalidOperationException($"python ha terminato con exit code {proc.ExitCode}");
}
private void Report(string message, int percent) => Progress?.Invoke(message, percent);
}