Files
whisper_web/tests/translate-real-model.test.ts
T

98 lines
3.9 KiB
TypeScript

import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import { join } from "node:path";
import {
assembleTranslation,
buildTranslationUnits,
getTranslationModel,
translateUnits,
} from "../src/lib/translation";
import { loadOpusMt, type OpusMtTranslator } from "../src/worker/opusMt";
/**
* Real Opus-MT translation on onnxruntime-node (D-WS4b), opt-in:
*
* MT_INTEGRATION=1 flock /tmp/whisper-heavy.lock bun test tests/translate-real-model.test.ts
*
* Downloads Xenova/opus-mt-en-da and -da-en (~113 MB each, q8) into the
* Transformers.js cache on first run; ~3 min on a 16-core CPU. Same loader
* and loop as the browser worker.
*/
const ENABLED = process.env.MT_INTEGRATION === "1";
const AUDIO = join(import.meta.dir, "audio_files");
const words = (text: string) => text.split(/\s+/).filter(Boolean);
describe.skipIf(!ENABLED)("Opus-MT on a real transcript", () => {
let enDa: OpusMtTranslator;
let daEn: OpusMtTranslator;
beforeAll(async () => {
enDa = await loadOpusMt(getTranslationModel("en", "da")!.id);
daEn = await loadOpusMt(getTranslationModel("da", "en")!.id);
}, 600_000);
afterAll(async () => {
await enDa?.dispose();
await daEn?.dispose();
});
const run = (translator: OpusMtTranslator, units: string[]) =>
translateUnits(units, {
countTokens: translator.countTokens,
translateBatch: (pieces) => translator.translateBatch(pieces),
});
test(
"all of en_alien → Danish: one translation per line, none empty, none copied from a neighbour",
async () => {
const text = await Bun.file(join(AUDIO, "en_alien.txt")).text();
const units = buildTranslationUnits({ text, chunks: [] });
const started = performance.now();
const out = await run(enDa, units.map((unit) => unit.text));
console.info(`[mt] en_alien en→da: ${((performance.now() - started) / 1000).toFixed(1)} s`);
const assembled = assembleTranslation(units, out);
expect(out).toHaveLength(units.length);
for (const line of out) expect(line.trim()).not.toBe("");
// Guards the batched-generation bug (every row = row 0's output).
const distinctSources = new Set(units.map((unit) => unit.text)).size;
expect(new Set(out).size).toBeGreaterThan(distinctSources * 0.9);
// Nothing dropped: a translation keeps roughly the source's length.
const ratio = words(assembled.text).length / words(text).length;
expect(ratio).toBeGreaterThan(0.6);
expect(ratio).toBeLessThan(1.5);
},
600_000,
);
test(
"an unpunctuated unit far beyond 512 tokens is translated whole (Review Focus 5)",
async () => {
const text = await Bun.file(join(AUDIO, "en_alien.txt")).text();
// Live-mode-like run-on: no sentence punctuation, one single unit.
const runOn = words(text.replace(/[.!?…]/g, "")).slice(0, 900).join(" ");
expect(enDa.countTokens(runOn)).toBeGreaterThan(512);
const [out] = await run(enDa, [runOn]);
const ratio = words(out ?? "").length / words(runOn).length;
expect(ratio).toBeGreaterThan(0.6);
expect(ratio).toBeLessThan(1.5);
},
600_000,
);
test(
"da_festuge → English keeps names and dates, timestamps carried over",
async () => {
const text = await Bun.file(join(AUDIO, "da_festuge.txt")).text();
const chunks = text
.split("\n")
.filter((line) => line.trim() !== "")
.map((line, index) => ({ text: ` ${line}`, timestamp: [index * 5, index * 5 + 5] as [number, number] }));
const units = buildTranslationUnits({ text, chunks });
const result = assembleTranslation(units, await run(daEn, units.map((unit) => unit.text)));
expect(result.text).toContain("Aarhus");
expect(result.text).toMatch(/August/);
expect(result.chunks.map((chunk) => chunk.timestamp)).toEqual(chunks.map((chunk) => chunk.timestamp));
},
600_000,
);
});