From 8e04d0196945c5173923fb72ecc665d40d122adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Wed, 8 Jul 2026 19:53:26 -0400 Subject: [PATCH] fix(cli): map ggml model stem to whisper.cpp dotted DTW preset (fixes large-v3) (#2086) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `hyperframes transcribe --model large-v3` aborted with "unknown DTW preset 'large-v3'". whisper.cpp's --dtw flag wants a dotted alignment-heads preset (large.v3), but we passed the hyphenated ggml file stem (large-v3). They coincide for tiny/base/small/medium(+.en) — why it slipped through — but diverge for the large-v* family. Map stem -> preset (- to .) so large-v1/v2/v3 (and large-v3-turbo) work; no-op for the others. Also fixes media-use, which shells to `hyperframes transcribe`. Claude-Session: https://claude.ai/code/session_01H5k87mPZ4d6yiFwcWSb8Vv Co-authored-by: Claude Opus 4.8 (1M context) --- packages/cli/src/whisper/transcribe.test.ts | 24 +++++++++++++++++++++ packages/cli/src/whisper/transcribe.ts | 17 ++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 packages/cli/src/whisper/transcribe.test.ts 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) {