import { createHash } from 'node:crypto' import { createReadStream, createWriteStream } from 'node:fs' import { mkdir, rename, rm, stat } from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { Readable } from 'node:stream' import { pipeline } from 'node:stream/promises' const MODEL_URL = 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin' const MODEL_BYTES = 77_691_713 const MODEL_SHA256 = 'be07e048e1e599ad46341c8d2a135645097a538221678b7acdd1b1919c6e1b21' const DEFAULT_OUTPUT = 'apps/mobile-rn/android/app/src/main/assets/models/ggml-tiny.bin' async function digestFile(path) { const hash = createHash('sha256') await pipeline(createReadStream(path), hash) return hash.digest('hex') } async function isVerifiedModel(path) { try { const metadata = await stat(path) if (!metadata.isFile() || metadata.size !== MODEL_BYTES) return false return (await digestFile(path)) === MODEL_SHA256 } catch (error) { if (error?.code === 'ENOENT') return false throw error } } async function downloadVerifiedModel(outputPath) { const temporaryPath = `${outputPath}.part-${process.pid}` await mkdir(dirname(outputPath), { recursive: true }) try { const response = await fetch(MODEL_URL, { redirect: 'follow', signal: AbortSignal.timeout(120_000) }) if (!response.ok || !response.body) { throw new Error(`whisper_model_download_failed:${response.status}`) } const declaredLength = Number(response.headers.get('content-length')) if (Number.isFinite(declaredLength) && declaredLength !== MODEL_BYTES) { throw new Error(`whisper_model_length_mismatch:${declaredLength}`) } await pipeline( Readable.fromWeb(response.body), createWriteStream(temporaryPath, { flags: 'wx', mode: 0o600 }) ) if (!(await isVerifiedModel(temporaryPath))) { throw new Error('whisper_model_checksum_mismatch') } await rm(outputPath, { force: true }) await rename(temporaryPath, outputPath) } catch (error) { await rm(temporaryPath, { force: true }).catch(() => undefined) throw error } } const outputPath = resolve(process.argv[2] ?? DEFAULT_OUTPUT) if (await isVerifiedModel(outputPath)) { console.log(`Whisper model verified: ${outputPath}`) } else { await downloadVerifiedModel(outputPath) console.log(`Whisper model downloaded and verified: ${outputPath}`) }