1. 概要
本記事では、Web Speech API を利用して ブラウザのみで日本語・英語の音声認識を実行できる Web アプリの実装構成を解説する。Google アカウントや API キーは不要で、Chrome ブラウザ上で完結する。
また、取得した音声文字起こしをそのまま ChatGPT に投げるためのプロンプトテンプレートを自動挿入する領域も備えており、実務での議事録作成・インタビュー書き起こし・研修記録など幅広い用途に適用できる。
2. システム構成
2.1 使用技術
-
Web Speech API(SpeechRecognition)
音声認識。Chromeでのみ安定動作。 -
JavaScript(ES6)
認識制御、UI、テキスト処理。 -
HTML/CSS
UIレイアウト。 -
ローカル保存(Blob + a.download)
文字起こしテキストを、PCへ.txtとして保存。
2.2 特徴
- APIキー不要・完全ローカル処理
- 日本語 / 英語を即時切替可能
- 認識結果の「二重書き込み」を防止
- テキスト領域へ ChatGPT 用プロンプトを自動挿入
- ボタン操作のみで
- 録音開始 / 停止
- 保存
- コピー
が可能
3. 実装のポイント
3.1 音声認識の初期化
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
recognition = new SR();
recognition.lang = lang;
recognition.interimResults = false;
recognition.continuous = true;
interimResults を false にする理由は、
「途中経過が何回も返ってくる → 同じ文章が重複して並ぶ」
という現象を防ぐためである。
3.2 二重書き込み防止ロジック
let lastFinalText = "";
if (finalText === lastFinalText) {
return;
}
lastFinalText = finalText;
Chrome の Web Speech API は同一文を繰り返し返す挙動があるため、
「最後に確定した文と同じなら無視」
というロジックで対処している。
3.3 言語切り替え
langSelect.onchange = () => {
const lang = langSelect.value;
const wasRunning = isRecognizing;
recognition.stop();
initRecognition(lang);
if (wasRunning) recognition.start();
output.value += `\n--- 言語を変更しました (${lang}) ---\n`;
};
録音中でも即時切替できるよう、
停止 → 再初期化 → 再開
のフローで処理している。
3.4 文字起こし結果の保存
const blob = new Blob([output.value], { type: "text/plain" });
a.download = "transcript_" + new Date().toISOString().replace(/[:.]/g, "-") + ".txt";
ローカルファイルとしてダウンロードできるため、
議事録・授業記録・研究インタビューなどのログ管理に適している。
4. ChatGPT 向けプロンプトテンプレート
初期状態で textarea に埋め込まれているテンプレート:
【ChatGPTへの依頼】
以下の「文字起こしテキスト」を自然な日本語に整形し、
意味が通るように段落化・句読点挿入・誤認識修正をしてください。
必要なら:
・要約
・話者ごとの整理
・読みやすい文章化
なども行ってください。
------------------------------
【文字起こしテキストここから】
これにより、録音停止後すぐ ChatGPT に貼り付けて処理できる。
実務では以下の作業が効率化される:
- 会議議事録の整形
- 研修/授業の要約
- インタビュー原稿化
- 研究の聞き取りデータの整理
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Local Speech Recognition + ChatGPT Prompt</title>
<style>
body {
font-family: sans-serif;
margin: 20px;
background: #f5f5f5;
}
button, select {
font-size: 18px;
padding: 10px 20px;
margin-right: 10px;
}
textarea {
width: 100%;
height: 450px;
font-size: 16px;
padding: 10px;
}
</style>
</head>
<body>
<h2>ローカル音声認識(ChatGPTプロンプト付き / 日本語・英語切替)</h2>
<label>言語:</label>
<select id="langSelect">
<option value="ja-JP">日本語</option>
<option value="en-US">English</option>
</select>
<button id="startBtn">開始</button>
<button id="stopBtn">停止</button>
<button id="saveBtn">保存</button>
<button id="copyBtn">コピー</button>
<p>
※Google アカウント不要・APIキー不要<br>
※Chrome推奨(Safari/Firefox不可)
</p>
<textarea id="output">
【ChatGPTへの依頼】
以下の「文字起こしテキスト」を自然な日本語に整形し、
意味が通るように段落化・句読点挿入・誤認識修正をしてください。
必要なら:
・要約
・話者ごとの整理
・読みやすい文章化
なども行ってください。
------------------------------
【文字起こしテキストここから】
</textarea>
<script>
let recognition;
let isRecognizing = false;
// ★ 重複防止用:最後に確定した文
let lastFinalText = "";
// 初期化
function initRecognition(lang) {
try {
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
recognition = new SR();
recognition.lang = lang;
recognition.interimResults = false; // ★ 二重起きやすい interim は切る
recognition.continuous = true;
} catch (e) {
alert("SpeechRecognition API がサポートされていません。Chrome を使用してください。");
}
recognition.onresult = (event) => {
let finalText = event.results[event.results.length - 1][0].transcript;
// ★ 二重防止:前回と同じなら無視
if (finalText === lastFinalText) {
return;
}
lastFinalText = finalText;
output.value += finalText + "\n";
output.scrollTop = output.scrollHeight;
};
recognition.onend = () => {
if (isRecognizing) recognition.start();
};
}
// 初期言語
initRecognition("ja-JP");
const output = document.getElementById("output");
const startBtn = document.getElementById("startBtn");
const stopBtn = document.getElementById("stopBtn");
const saveBtn = document.getElementById("saveBtn");
const copyBtn = document.getElementById("copyBtn");
const langSelect = document.getElementById("langSelect");
// 言語変更
langSelect.onchange = () => {
const lang = langSelect.value;
const wasRunning = isRecognizing;
if (isRecognizing) {
recognition.stop();
isRecognizing = false;
}
initRecognition(lang);
if (wasRunning) {
recognition.start();
isRecognizing = true;
}
output.value += `\n--- 言語を変更しました (${lang}) ---\n`;
};
startBtn.onclick = () => {
if (!isRecognizing) {
lastFinalText = ""; // ★ 重複チェックリセット
recognition.start();
isRecognizing = true;
output.value += "\n--- 録音開始 ---\n";
}
};
stopBtn.onclick = () => {
if (isRecognizing) {
recognition.stop();
isRecognizing = false;
output.value += "\n--- 録音停止 ---\n";
}
};
// 保存
saveBtn.onclick = () => {
const blob = new Blob([output.value], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "transcript_" + new Date().toISOString().replace(/[:.]/g, "-") + ".txt";
a.click();
URL.revokeObjectURL(url);
};
// コピー
copyBtn.onclick = () => {
output.select();
document.execCommand("copy");
alert("コピーしました");
};
</script>
</body>
</html>