mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 14:55:37 +00:00
156 lines
3.9 KiB
TypeScript
156 lines
3.9 KiB
TypeScript
import { createWriteStream } from "node:fs";
|
|
import { constants as fsConstants } from "node:fs";
|
|
import fs from "node:fs/promises";
|
|
import { get as httpsGet } from "node:https";
|
|
import type Electron from "electron";
|
|
import {
|
|
WHISPER_MODEL_DIR,
|
|
WHISPER_MODEL_DOWNLOAD_URL,
|
|
WHISPER_SMALL_MODEL_PATH,
|
|
} from "../constants";
|
|
|
|
export function sendWhisperModelDownloadProgress(
|
|
webContents: Electron.WebContents,
|
|
payload: {
|
|
status: "idle" | "downloading" | "downloaded" | "error";
|
|
progress: number;
|
|
path?: string | null;
|
|
error?: string;
|
|
},
|
|
) {
|
|
webContents.send("whisper-small-model-download-progress", payload);
|
|
}
|
|
|
|
export async function getWhisperSmallModelStatus() {
|
|
try {
|
|
await fs.access(WHISPER_SMALL_MODEL_PATH, fsConstants.R_OK);
|
|
return {
|
|
success: true,
|
|
exists: true,
|
|
path: WHISPER_SMALL_MODEL_PATH,
|
|
};
|
|
} catch {
|
|
return {
|
|
success: true,
|
|
exists: false,
|
|
path: null,
|
|
};
|
|
}
|
|
}
|
|
|
|
export function downloadFileWithProgress(
|
|
url: string,
|
|
destinationPath: string,
|
|
onProgress: (progress: number) => void,
|
|
): Promise<void> {
|
|
const request = (currentUrl: string, redirectCount = 0): Promise<void> => {
|
|
return new Promise((resolve, reject) => {
|
|
const req = httpsGet(currentUrl, { timeout: 30_000 }, (response) => {
|
|
const statusCode = response.statusCode ?? 0;
|
|
const location = response.headers.location;
|
|
|
|
if (statusCode >= 300 && statusCode < 400 && location) {
|
|
response.resume();
|
|
if (redirectCount >= 5) {
|
|
reject(new Error("Too many redirects while downloading Whisper model."));
|
|
return;
|
|
}
|
|
|
|
const nextUrl = new URL(location, currentUrl).toString();
|
|
void request(nextUrl, redirectCount + 1)
|
|
.then(resolve)
|
|
.catch(reject);
|
|
return;
|
|
}
|
|
|
|
if (statusCode < 200 || statusCode >= 300) {
|
|
response.resume();
|
|
reject(new Error(`Whisper model download failed with status ${statusCode}.`));
|
|
return;
|
|
}
|
|
|
|
const totalBytes = Number.parseInt(
|
|
String(response.headers["content-length"] ?? "0"),
|
|
10,
|
|
);
|
|
let downloadedBytes = 0;
|
|
const fileStream = createWriteStream(destinationPath);
|
|
|
|
response.on("data", (chunk: Buffer) => {
|
|
downloadedBytes += chunk.length;
|
|
if (Number.isFinite(totalBytes) && totalBytes > 0) {
|
|
onProgress(Math.min(100, Math.round((downloadedBytes / totalBytes) * 100)));
|
|
}
|
|
});
|
|
|
|
response.on("error", (error) => {
|
|
fileStream.destroy(error);
|
|
});
|
|
|
|
fileStream.on("error", (error) => {
|
|
response.destroy(error);
|
|
reject(error);
|
|
});
|
|
|
|
fileStream.on("finish", () => {
|
|
onProgress(100);
|
|
resolve();
|
|
});
|
|
|
|
response.pipe(fileStream);
|
|
});
|
|
|
|
req.on("error", reject);
|
|
req.on("timeout", () => {
|
|
req.destroy(new Error("Whisper model download timed out."));
|
|
});
|
|
});
|
|
};
|
|
|
|
return request(url);
|
|
}
|
|
|
|
export async function downloadWhisperSmallModel(
|
|
webContents: Electron.WebContents,
|
|
): Promise<string> {
|
|
await fs.mkdir(WHISPER_MODEL_DIR, { recursive: true });
|
|
const tempPath = `${WHISPER_SMALL_MODEL_PATH}.download`;
|
|
|
|
sendWhisperModelDownloadProgress(webContents, {
|
|
status: "downloading",
|
|
progress: 0,
|
|
path: null,
|
|
});
|
|
|
|
try {
|
|
await fs.rm(tempPath, { force: true });
|
|
await downloadFileWithProgress(WHISPER_MODEL_DOWNLOAD_URL, tempPath, (progress) => {
|
|
sendWhisperModelDownloadProgress(webContents, {
|
|
status: "downloading",
|
|
progress,
|
|
path: null,
|
|
});
|
|
});
|
|
await fs.rename(tempPath, WHISPER_SMALL_MODEL_PATH);
|
|
sendWhisperModelDownloadProgress(webContents, {
|
|
status: "downloaded",
|
|
progress: 100,
|
|
path: WHISPER_SMALL_MODEL_PATH,
|
|
});
|
|
return WHISPER_SMALL_MODEL_PATH;
|
|
} catch (error) {
|
|
await fs.rm(tempPath, { force: true }).catch(() => undefined);
|
|
sendWhisperModelDownloadProgress(webContents, {
|
|
status: "error",
|
|
progress: 0,
|
|
path: null,
|
|
error: String(error),
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function deleteWhisperSmallModel(): Promise<void> {
|
|
await fs.rm(WHISPER_SMALL_MODEL_PATH, { force: true });
|
|
}
|