diff --git a/packages/cli/src/whisper/transcribe.test.ts b/packages/cli/src/whisper/transcribe.test.ts new file mode 100644 index 000000000..4678f41f1 --- /dev/null +++ b/packages/cli/src/whisper/transcribe.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, test } from "vitest"; +import { dtwPresetForModel } from "./transcribe.js"; + +describe("dtwPresetForModel", () => { + // The large family is the regression: model files are hyphenated but + // whisper.cpp's --dtw preset is dotted, so `--dtw large-v3` used to abort + // with "unknown DTW preset 'large-v3'". + test.each([ + ["large-v3", "large.v3"], + ["large-v2", "large.v2"], + ["large-v1", "large.v1"], + ["large-v3-turbo", "large.v3.turbo"], + ])("maps hyphenated large model %s to dotted preset %s", (model, preset) => { + expect(dtwPresetForModel(model)).toBe(preset); + }); + + // tiny/base/small/medium (+.en) already match their preset — must be unchanged. + test.each(["tiny", "base.en", "small.en", "medium.en", "small"])( + "leaves preset-identical model %s unchanged", + (model) => { + expect(dtwPresetForModel(model)).toBe(model); + }, + ); +}); diff --git a/packages/cli/src/whisper/transcribe.ts b/packages/cli/src/whisper/transcribe.ts index 999724473..96c8ec48f 100644 --- a/packages/cli/src/whisper/transcribe.ts +++ b/packages/cli/src/whisper/transcribe.ts @@ -205,6 +205,21 @@ function prepareAudio(audioPath: string): string { return wavPath; } +/** + * Map a ggml model file-stem to whisper.cpp's `--dtw` alignment-heads preset. + * + * The two mostly coincide, so the stem was long passed straight to `--dtw` — but + * they diverge for the large family: the model files are hyphenated + * (`ggml-large-v3.bin`) while the DTW presets are dotted (`large.v3`, + * `large.v3.turbo`). `--dtw large-v3` makes whisper-cli abort with + * "unknown DTW preset 'large-v3'", surfacing as "Transcription failed". The + * tiny/base/small/medium (+`.en`) families have no hyphen, so `-`→`.` is a no-op + * for them and correct for the large family. + */ +export function dtwPresetForModel(model: string): string { + return model.replace(/-/g, "."); +} + /** * Transcribe an audio or video file and save transcript.json to the output directory. */ @@ -281,7 +296,7 @@ export async function transcribe( "--output-file", outputBase, "--dtw", - effectiveModel, + dtwPresetForModel(effectiveModel), "--suppress-nst", ]; if (detectedLanguage) {