feat(release): prepare 1.1.0 candidate
This commit is contained in:
parent
5a34f66981
commit
5205dcdfa9
736 changed files with 115667 additions and 12203 deletions
72
scripts/ci/prepare-whisper-model.mjs
Normal file
72
scripts/ci/prepare-whisper-model.mjs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
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}`)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue