OpenAIの文字起こしAPIは、Node.jsから音声ファイルを送信し、テキストを受け取れます。公式SDKの audio.transcriptions.create() へファイルとモデルを渡すのが最小構成です。
会議やインタビューで使う場合は、文字起こしだけでなく、録音同意、話者、タイムスタンプ、固有名詞、長時間ファイル、保存方針まで設計します。
この記事では、ファイル文字起こしの最小コード、日本語の整形、話者分離、長い音声の分割、期待値と差分を残す再現テストを解説します。
情報確認日:2026年7月17日(日本時間)
結論:ファイルAPIとRealtimeを用途で分ける
| 方式 | 向く用途 | 特徴 |
|---|---|---|
| Transcriptions API | 録音済みファイル、議事録、字幕 | ファイル単位で実装しやすい |
| Realtime transcription | 通話中の字幕、ライブ入力 | 音声streamとイベント処理が必要 |
最初は録音済みの短いファイルでTranscriptions APIを試し、認証、形式、モデル、出力を確認します。
Node.jsプロジェクトを準備する
mkdir openai-transcription-sample
cd openai-transcription-sample
npm init -y
npm pkg set type=module
npm pkg set scripts.start="node transcribe.mjs"
npm install openai
export OPENAI_API_KEY="your_api_key_here"
node_modules/
audio/
transcripts/
.env
.env.*
!.env.example
音声には個人情報が含まれやすいため、サンプル音声や文字起こし結果をGitへ含めるかは用途に合わせて決めます。APIキーはブラウザへ置かず、サーバー側の環境変数から読み取ります。
音声ファイルを文字起こしする
transcribe.mjsを作ります。
import fs from "node:fs";
import OpenAI from "openai";
if (!process.env.OPENAI_API_KEY) {
throw new Error("OPENAI_API_KEY is not set");
}
const filePath = process.argv[2];
if (!filePath || !fs.existsSync(filePath)) {
throw new Error("Usage: npm start -- path/to/audio.mp3");
}
const client = new OpenAI();
const model =
process.env.OPENAI_TRANSCRIBE_MODEL || "gpt-4o-transcribe";
const transcription = await client.audio.transcriptions.create({
file: fs.createReadStream(filePath),
model,
language: "ja",
});
console.log(transcription.text);
npm start -- audio/sample.mp3
API Referenceで案内される対応形式にはflac、mp3、mp4、mpeg、mpga、m4a、ogg、wav、webmがあります。制限やモデル対応は更新されるため、大きなファイルを送る前に現行ガイドを確認してください。
日本語の結果を整形する
文字起こしと、議事録用の整形・要約は分けます。まず原文に近いtranscriptを保存し、別処理で句読点、改行、用語置換、要約を行います。
function normalizeJapanese(text) {
return text
.normalize("NFKC")
.replace(/[ \t]+/g, " ")
.replace(/\s*\n\s*/g, "\n")
.trim();
}
console.log(normalizeJapanese(transcription.text));
固有名詞を機械置換する場合は、元のtranscriptを残し、置換一覧と理由を記録します。要約だけを保存すると、後から誤変換を確認できません。
話者分離を使う
複数話者を区別したい場合は、確認時点で gpt-4o-transcribe-diarize が話者分離用モデルとしてAPI Referenceに掲載されています。通常のtext応答ではなく、話者と区間を持つ出力を処理します。
自動話者ラベルを実名とみなさず、Speaker 0、Speaker 1と時刻を確認してから、人が氏名を対応付けます。似た声、重なり、雑音で誤る可能性があります。
| 確認 | 理由 |
|---|---|
| 話者の切替時刻 | 発話の重なりで境界がずれる |
| 氏名の割当 | モデルのラベルは本人確認ではない |
| 短い相づち | 別話者へ誤割当されやすい |
| 重要発言 | 決定事項は音声を聞き直す |
長い音声を分割する
長時間音声は、単純に同じ秒数で切ると単語や文の途中で分かれます。無音区間を候補にし、少し重複させて分割し、開始時刻をメタデータとして持ちます。
音声
├─ chunk-001 00:00:00〜00:10:05
├─ chunk-002 00:09:55〜00:20:02
└─ chunk-003 00:19:52〜00:28:40
重複10秒を照合し、同じ文を1回だけ残す。
- 無音検出で分割候補を作る
- 各chunkへ元音声の開始時刻を付ける
- 前後を数秒重ねる
- 並列数とrate limitを守って送信する
- 重複部分を文字列と時刻で照合する
- 結合後に重要箇所を音声と突き合わせる
日本語音声の再現テスト
次の文を自分の声で録音し、audio/fixture-ja.wavとして保存します。第三者の音声は同意なく使わないでください。
本日の会議では、公開日を7月24日に変更しました。
担当は佐藤さんで、確認期限は7月21日です。
| 項目 | 記録 |
|---|---|
| 期待値 | 上記2文。日付、氏名、担当関係が必須 |
| 入力 | fixture-ja.wav、1話者、静かな室内 |
| モデル | OPENAI_TRANSCRIBE_MODELの値 |
| 実行結果 | 制作時はAPIキー未使用のため未取得。実行者が保存 |
| 合格 | 24日、佐藤、21日が正しく、意味が変わらない |
compare.mjsで正規化後の差を確認します。
import fs from "node:fs";
const expected = fs.readFileSync("expected.txt", "utf8");
const actual = fs.readFileSync("actual.txt", "utf8");
const normalize = (text) =>
text.normalize("NFKC").replace(/[\s、。,.]/g, "");
console.log({
exactAfterNormalize:
normalize(expected) === normalize(actual),
requiredTerms: ["24日", "佐藤", "21日"].map((term) => ({
term,
found: actual.includes(term),
})),
});
完全一致だけでなく、日付、氏名、否定、数量など、業務上重要な語を個別に採点します。モデル変更時に同じ音声で再実行します。
プライバシーと運用の注意
- 録音対象者へ目的、保存、共有範囲を説明する
- アップロード前に不要な個人情報を除く
- 音声、原文、整形版、要約の保存期間を分ける
- APIのデータ利用・保持設定を現行文書で確認する
- 重要な決定事項は音声と人が照合する
- 誤変換の訂正履歴を残す
会議全体の録音から要約・確認までの運用はAI議事録の作り方も参考になります。
まとめ
OpenAI文字起こしAPIは、公式SDKの audio.transcriptions.create() へ音声fileとmodelを渡して利用します。まず短い日本語fixtureで、固有名詞・日付・否定を含む期待値と差分を記録します。
長い音声は無音区間と重複を使って分割し、話者分離や要約を別工程にしてください。録音同意、権限、保存、訂正も実装の一部です。