diff --git a/packages/cli/src/commands/init.test.ts b/packages/cli/src/commands/init.test.ts
index 13a86b090..3d2a25dbc 100644
--- a/packages/cli/src/commands/init.test.ts
+++ b/packages/cli/src/commands/init.test.ts
@@ -11,6 +11,7 @@ import {
} from "./init.js";
const cliEntry = resolve(fileURLToPath(import.meta.url), "..", "..", "cli.ts");
+const initSource = readFileSync(new URL("./init.ts", import.meta.url), "utf-8");
const tailwindScript =
'';
@@ -47,6 +48,14 @@ function expectScaffoldedScripts(target: string): void {
}
describe("hyperframes init flag rename", () => {
+ it("selects the language-compatible model before both eager init downloads", () => {
+ expect(initSource).toMatch(
+ /const initialTranscriptionModel = initialModelForLanguage\(\s*modelFlag \?\? DEFAULT_MODEL,\s*languageFlag,?\s*\);/,
+ );
+ expect(initSource.match(/await ensureModel\(initialTranscriptionModel/g)).toHaveLength(2);
+ expect(initSource).not.toMatch(/await ensureModel\(modelFlag/g);
+ });
+
it("requires an explicit source in non-interactive mode", () => {
const dir = mkdtempSync(join(tmpdir(), "hf-init-test-"));
const target = join(dir, "proj");
diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts
index 921320d68..709112df5 100644
--- a/packages/cli/src/commands/init.ts
+++ b/packages/cli/src/commands/init.ts
@@ -45,7 +45,8 @@ import {
} from "../templates/generators.js";
import { fetchRemoteTemplate } from "../templates/remote.js";
import { trackInitTemplate } from "../telemetry/events.js";
-import { hasFFmpeg } from "../whisper/manager.js";
+import { DEFAULT_MODEL, hasFFmpeg } from "../whisper/manager.js";
+import { initialModelForLanguage } from "../whisper/transcribe.js";
import { findFFmpeg, findFFprobe, getFFmpegInstallHint } from "../browser/ffmpeg.js";
import { VERSION } from "../version.js";
import {
@@ -769,6 +770,10 @@ export default defineCommand({
const nonInteractive = args["non-interactive"] === true;
const modelFlag = args.model;
const languageFlag = args.language;
+ const initialTranscriptionModel = initialModelForLanguage(
+ modelFlag ?? DEFAULT_MODEL,
+ languageFlag,
+ );
const interactive = !nonInteractive && process.stdout.isTTY === true;
if (skipSkillsFlagIgnored) {
@@ -866,7 +871,7 @@ export default defineCommand({
try {
const { ensureWhisper, ensureModel } = await import("../whisper/manager.js");
await ensureWhisper();
- await ensureModel(modelFlag);
+ await ensureModel(initialTranscriptionModel);
console.log("Transcribing...");
const { transcribe: runTranscribe } = await import("../whisper/transcribe.js");
const result = await runTranscribe(sourceFilePath, destDir, {
@@ -1044,7 +1049,7 @@ export default defineCommand({
await ensureWhisper({
onProgress: (msg) => spin.message(msg),
});
- await ensureModel(modelFlag, {
+ await ensureModel(initialTranscriptionModel, {
onProgress: (msg) => spin.message(msg),
});
diff --git a/packages/cli/src/whisper/transcribe.test.ts b/packages/cli/src/whisper/transcribe.test.ts
index 370988a4a..b6ad787a9 100644
--- a/packages/cli/src/whisper/transcribe.test.ts
+++ b/packages/cli/src/whisper/transcribe.test.ts
@@ -1,6 +1,7 @@
import { describe, expect, it, test } from "vitest";
import {
dtwPresetForModel,
+ initialModelForLanguage,
isWhisperTimeoutError,
resolveAudioPreparationTimeoutMs,
resolveWhisperTimeoutMs,
@@ -245,3 +246,21 @@ describe("resolveAudioPreparationTimeoutMs", () => {
expect(resolveAudioPreparationTimeoutMs(Number.NaN)).toBe(120_000);
});
});
+
+describe("initialModelForLanguage", () => {
+ test("keeps the default English-only model when no language is specified", () => {
+ expect(initialModelForLanguage("small.en", undefined)).toBe("small.en");
+ });
+
+ test("uses the multilingual model before downloading for explicit German", () => {
+ expect(initialModelForLanguage("small.en", "de")).toBe("small");
+ });
+
+ test("keeps English-only models for English locale variants", () => {
+ expect(initialModelForLanguage("small.en", "en-US")).toBe("small.en");
+ });
+
+ test("keeps an already multilingual model unchanged", () => {
+ expect(initialModelForLanguage("large-v3", "de")).toBe("large-v3");
+ });
+});
diff --git a/packages/cli/src/whisper/transcribe.ts b/packages/cli/src/whisper/transcribe.ts
index 7d71614df..6641edb95 100644
--- a/packages/cli/src/whisper/transcribe.ts
+++ b/packages/cli/src/whisper/transcribe.ts
@@ -386,6 +386,14 @@ export function dtwPresetForModel(model: string): string {
return model.replace(/-/g, ".");
}
+export function initialModelForLanguage(model: string, language?: string): string {
+ const baseLanguage = language?.trim().toLowerCase().split(/[-_]/, 1)[0];
+ if (baseLanguage && baseLanguage !== "en" && model.endsWith(".en")) {
+ return model.slice(0, -3);
+ }
+ return model;
+}
+
/**
* Transcribe an audio or video file and save transcript.json to the output directory.
*/
@@ -395,7 +403,7 @@ export async function transcribe(
outputDir: string,
options?: TranscribeOptions,
): Promise {
- const model = options?.model ?? DEFAULT_MODEL;
+ const model = initialModelForLanguage(options?.model ?? DEFAULT_MODEL, options?.language);
// 1. Ensure whisper binary
options?.onProgress?.("Checking whisper...");