Created
August 26, 2026 09:48
-
-
Save jdegger/9e703ef075565d3945a6e893f787f1ab to your computer and use it in GitHub Desktop.
OpenAI gpt-transcribe example: Dutch WAV + BHV-Knop terms (no API keys)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Example: Dutch speech-to-text with OpenAI `gpt-transcribe`. | |
| * | |
| * export OPENAI_API_KEY=... | |
| * npx tsx transcribe-bhv.ts ./clip.wav | |
| * | |
| * Do not hardcode API keys. Restricted project keys need audio transcription | |
| * scope; `whisper-1` may be blocked even when `gpt-transcribe` works. | |
| */ | |
| import { readFile } from "node:fs/promises"; | |
| import { basename } from "node:path"; | |
| const TRANSCRIBE_URL = "https://api.openai.com/v1/audio/transcriptions"; | |
| const MODEL = "gpt-transcribe"; | |
| /** Prompt should be in the same language as the audio. */ | |
| const PROMPT = | |
| "Nederlandse opname over BHV-Knop.nl, een compleet BHV-oproepsysteem. " + | |
| "Vaak genoemde termen: BHV, BHV-er, BHV-ers, BHV-Knop, BHV-app, " + | |
| "BHV-organisatie, BHV-oproepsysteem, BHV-groepsgesprek, BHV-plan, " + | |
| "basisstation, aanwezigheidskiosk, kiosk, alarmafhandeling, " + | |
| "aanwezigheidsdetectie, bedrijfshulpverlening, bedrijfsongeval, " + | |
| "portofoon, aanmelden, afmelden, centrale post, akoestisch signaal, " + | |
| "groepsgesprek."; | |
| /** Literal terms that STT otherwise mangles (BHV, knop, kiosk, …). */ | |
| const KEYWORDS = [ | |
| "BHV", | |
| "BHV-Knop", | |
| "BHV-Knop.nl", | |
| "BHV-app", | |
| "BHV-oproepsysteem", | |
| "BHV-organisatie", | |
| "BHV-groepsgesprek", | |
| "BHV-plan", | |
| "bedrijfshulpverlening", | |
| "bedrijfsongeval", | |
| "basisstation", | |
| "aanwezigheidskiosk", | |
| "aanwezigheidsdetectie", | |
| "alarmafhandeling", | |
| "portofoon", | |
| "kiosk", | |
| "aanmelden", | |
| "afmelden", | |
| "centrale post", | |
| "akoestisch signaal", | |
| "groepsgesprek", | |
| ]; | |
| type TranscriptionResponse = { | |
| text: string; | |
| languages?: { code: string }[]; | |
| usage?: { type: string; seconds?: number }; | |
| error?: { message: string; code?: string }; | |
| }; | |
| async function transcribe(audioPath: string, apiKey: string): Promise<TranscriptionResponse> { | |
| const bytes = await readFile(audioPath); | |
| const form = new FormData(); | |
| form.set("model", MODEL); | |
| form.set("prompt", PROMPT); | |
| form.set( | |
| "file", | |
| new Blob([bytes], { type: "audio/wav" }), | |
| basename(audioPath), | |
| ); | |
| // `gpt-transcribe` uses `languages` (list), not `language`. | |
| form.append("languages[]", "nl"); | |
| for (const keyword of KEYWORDS) { | |
| form.append("keywords[]", keyword); | |
| } | |
| const response = await fetch(TRANSCRIBE_URL, { | |
| method: "POST", | |
| headers: { Authorization: `Bearer ${apiKey}` }, | |
| body: form, | |
| }); | |
| const body = (await response.json()) as TranscriptionResponse; | |
| if (!response.ok) { | |
| throw new Error(body.error?.message ?? `HTTP ${response.status}`); | |
| } | |
| return body; | |
| } | |
| const apiKey = process.env.OPENAI_API_KEY; | |
| const audioPath = process.argv[2]; | |
| if (!apiKey) { | |
| throw new Error("Set OPENAI_API_KEY in the environment."); | |
| } | |
| if (!audioPath) { | |
| throw new Error("Usage: npx tsx transcribe-bhv.ts ./clip.wav"); | |
| } | |
| const result = await transcribe(audioPath, apiKey); | |
| console.log(result.text); | |
| if (result.languages?.length) { | |
| console.error("languages:", result.languages.map((item) => item.code).join(", ")); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment