この記事は、CPS Lab Advent Calendar 2018の5日目の記事です。
音声認識編 → https://qiita.com/hmmrjn/items/4b77a86030ed0071f548
はじめに
みなさん、ウェブサイトで音声合成(読み上げ)、音声認識を使いたいとなった時、どうしますか?
- Google Cloud Speech API
- Microsoft Azure Text to Speech API
- IBM Watson Text to Speech API
- AWS Amazon Polly
などのがつがつとしたWebサービスが思いつくのではないでしょうか?
でも、登録しないといけないし、お金かかりそうですよね...
実はそれ、最近のブラウザであれば、無料でできるんです。
Web Speech API とは
自分の周りでは意外と知っている人が少なかったので紹介します。
- Webページで、ブラウザの音声認識(マイクの音声を文章に変換)、音声合成(文章を読み上げる)機能を使うためのAPI。
- 2012年にWC3から仕様が策定された。(が、最近になってやっとブラウザが対応してきている。)
- Webサービスじゃないので、お金も、登録も、認証キーも一切いらない。
- 軽いJavaScriptの知識があれば誰でも使える。
- HTML/CSS/JSだけの静的ページでも動く。(ブラウザ上で動くので、バックエンドサーバは不要。)
- ブラウザのネイティブAPIなので、JavaScriptライブラリをインポートする必要もなし。
- ただ、音声合成はブラウザによって使える声が違って、音声認識は現時点では、Chromeしか対応していない...。
Demo
一見は百聞に如かず。
※ iPhoneの場合はマナーモードを解除しないと音声は流れません。
ブラウザの対応状況
音声合成 (Speech Synthesis)
https://caniuse.com/#feat=speech-synthesis オッ。Internet Explorer 以外はほとんどが対応してる。音声認識 (Speech Recognition)
https://caniuse.com/#feat=speech-recognition ヒィー。Chromeしか対応してないし、部分対応だし、なんだこれ。 ただ、FirefoxとOperaは `In development` 中らしいので、 今後の動きに期待したい。[詳細](https://developer.microsoft.com/en-us/microsoft-edge/platform/status/webspeechapispeechrecognition/)音声認識 (Speech Recognition)
記事が長くなったので、次週やります。
https://qiita.com/hmmrjn/items/4b77a86030ed0071f548
音声合成 (SpeechSynthesis)
文字列をブラウザに声で読んでもらう機能です。
対応確認
対応していないブラウザは少ないですが、ユーザのブラウザが対応しているかどうか調べたい場合はこちら。
https://jsfiddle.net/hmmrjn/o68vgqh0/1/
<script>
if ('speechSynthesis' in window) {
alert("このブラウザは音声合成に対応しています。🎉")
} else {
alert("このブラウザは音声合成に対応していません。😭")
}
</script>
Hello World!
以下の2行で、ページをリロードするたび、ブラウザに「ハローワールド!」と喋らせることができます。
サーバ起動する必要なし。index.htmlファイルでも動きます。素敵ですね。
<script>
// 発言を作成
const uttr = new SpeechSynthesisUtterance("Hello World!")
// 発言を再生 (発言キューに発言を追加)
speechSynthesis.speak(uttr)
</script>
入力欄の文章を読み上げる
ボタンをクリックしたときに、入力欄の中身を読み上げたい場合は以下となります。
ちなみに連打すると、連打した分だけ、続けて読み上げます。
https://jsfiddle.net/hmmrjn/wafd3sum/
<textarea id="text">再生する内容</textarea>
<button id="speak-btn">再生</button>
<script>
const text = document.querySelector('#text')
const speakBtn = document.querySelector('#speak-btn')
speakBtn.addEventListener('click', function() {
// 発言を作成
const uttr = new SpeechSynthesisUtterance(text.value)
// 発言を再生 (発言キューに発言を追加)
speechSynthesis.speak(uttr)
})
</script>
再生、停止、一時停止、再生再開
長文を読ませると、途中で黙らせるボタンが欲しくなります。
ここも直感的でとてもわかりやすいです。
speechSynthesis
のメソッド
メソッド | 意味 |
---|---|
.speak(uttr) |
発言を再生 (発言キュー発言に追加) |
.cancel() |
再生停止 (発言キューをクリアして止まる) |
.pause() |
一時停止 (発言キューを保持して止まる) |
.resume() |
再生再開 (一時停止を解除) |
https://jsfiddle.net/hmmrjn/dmbhr6yv/
<textarea id="text">
アドベントカレンダーは、クリスマスまでの日数を数えるためのカレンダー。
イエス・キリストの降誕を待ち望む24日間、窓を毎日ひとつずつ開けていき、
中には、イラストや物語の一編、甘いお菓子、小さな贈り物などが入っている。
</textarea>
<button id="speak-btn">再生</button>
<button id="cancel-btn">停止</button>
<button id="pause-btn">一時停止</button>
<button id="resume-btn">再開</button>
<script>
const text = document.querySelector('#text')
const speakBtn = document.querySelector('#speak-btn')
const cancelBtn = document.querySelector('#cancel-btn')
const pauseBtn = document.querySelector('#pause-btn')
const resumeBtn = document.querySelector('#resume-btn')
speakBtn.addEventListener('click', function() {
// 発言を作成
const uttr = new SpeechSynthesisUtterance(text.value)
// 発言を再生 (発言キュー発言に追加)
speechSynthesis.speak(uttr)
})
cancelBtn.addEventListener('click', function() {
// 再生停止 (発言キューをクリアして止まる)
speechSynthesis.cancel()
})
pauseBtn.addEventListener('click', function() {
// 一時停止 (発言キューを保持して止まる)
speechSynthesis.pause()
})
resumeBtn.addEventListener('click', function() {
// 再生再開 (一時停止を解除)
speechSynthesis.resume()
})
</script>
言語、速度、高さ、音量を指定
HALL 9000 っぽい声にしてみる。
// 発言を作成
const uttr = new SpeechSynthesisUtterance()
// 文章 (コンストラクタの引数以外に、この方法でも指定できます)
uttr.text = "Hi, Dave."
// 言語 (日本語:ja-JP, アメリカ英語:en-US, イギリス英語:en-GB, 中国語:zh-CN, 韓国語:ko-KR)
uttr.lang = "en-US"
// 速度 0.1-10 初期値:1 (倍速なら2, 半分の倍速なら0.5)
uttr.rate = 0.5
// 高さ 0-2 初期値:1
uttr.pitch = 0.5
// 音量 0-1 初期値:1
uttr.volume = 0.75
// 再生 (発言キュー発言に追加)
speechSynthesis.speak(uttr)
声の種類の指定
同じ言語でも、様々な声の種類が存在するときがあります。
例えば、男性と女性の声だったり、Appleが提供する声とGoogleが提供する声だったりします。
ただ、この指定の仕方が少し複雑だったので、ここは丁寧にみていきましょう。
コード | 型 | 説明 | |
---|---|---|---|
① | speechSynthesis.getVoices() |
SpeechSynthesisVoice[] | ブラウザで使える声の配列を得る。 |
② | speechSynthesis.onvoiceschanged = e => {...} |
コールバック関数 | Chromeは使える声を非同期で読み込むため、使える声が増えたときに着火するイベント(これ)が必要となる。 |
③ | uttr.voice = voice |
SpeechSynthesisVoice | 使う声を指定する。 |
流れとしては、
① getVoices()
で、使える声の配列を得て、
② 使える声が増えたらまた getVoices()
で、使える声の配列を得て、
③ その配列の中から使いたい声を uttr.voice =
に渡す形となります。
ちなみに、声のオブジェクト (SpeechSynthesisVoice
) の中身はこんな感じです。
[object SpeechSynthesisVoice] {
name: "Google 日本語", // 声の名前 (Google 日本語, Alice, Kyokoとか。ブラウザによって使える声が違う。)
lang: "ja-JP", // 声の言語 (BCP47言語タグ:ja-JP, en-US, en-GB, zh-CN, ko-KR とか。)
default: false, // ブラウザの既定の声か。(自分のChromeの場合、Kyokoがデフォルトだった。)
localService: false, // true:ローカル(OS)の声 false:リモート(ネット上)の声
voiceURI: "Google 日本語" // WebサービスのURI (のはずなんだが、Chromeではなぜか声の名前が入ってる。気にしない。)
}
たとえば、selectboxで声の種類を選べるようにしたい場合こうなります。
https://jsfiddle.net/hmmrjn/u1y4aL9v/
<textarea id="text">Hello. Nice to meet you.</textarea>
<select id="voice-select"></select>
<button id="speak-btn">再生</button>
<script>
const text = document.querySelector('#text')
const voiceSelect = document.querySelector('#voice-select')
const speakBtn = document.querySelector('#speak-btn')
// selectタグの中身を声の名前が入ったoptionタグで埋める
function appendVoices() {
// ① 使える声の配列を取得
// 配列の中身は SpeechSynthesisVoice オブジェクト
const voices = speechSynthesis.getVoices()
voiceSelect.innerHTML = ''
voices.forEach(voice => { // アロー関数 (ES6)
// 日本語と英語以外の声は選択肢に追加しない。
if(!voice.lang.match('ja|en-US')) return
const option = document.createElement('option')
option.value = voice.name
option.text = `${voice.name} (${voice.lang})` // テンプレートリテラル (ES6)
option.setAttribute('selected', voice.default)
voiceSelect.appendChild(option)
});
}
appendVoices()
// ② 使える声が追加されたときに着火するイベントハンドラ。
// Chrome は非同期に(一個ずつ)声を読み込むため必要。
speechSynthesis.onvoiceschanged = e => {
appendVoices()
}
speakBtn.addEventListener('click', function() {
// 発言を作成
const uttr = new SpeechSynthesisUtterance(text.value)
// ③ 選択された声を指定
uttr.voice = speechSynthesis
.getVoices()
.filter(voice => voice.name === voiceSelect.value)[0]
// 発言を再生 (発言キュー発言に追加)
speechSynthesis.speak(uttr)
})
</script>
使える声
このリンクで今のブラウザでどんな声が使えるかみれます。
https://codepen.io/rodhamjun/full/jQJEWQ/
自分の環境(Google Chrome/macOS Mojave)では、色んな言語の声があって、合わせて65個の声が使えました。
比較のため、macOSとWindowsとChromeで使える声を下に貼っておきます。
※多いので、英語と日本語の声のみで。
macOS
macOS 10.14 Mojave
name | lang | localService | default |
---|---|---|---|
Kyoko | ja-JP | true | true |
Alex | en-US | true | false |
Daniel | en-GB | true | false |
Fiona | en | true | false |
Fred | en-US | true | false |
Windows
Windows 10 Home 1803
name | lang | localService | default |
---|---|---|---|
Microsoft Ayumi - Japanese (Japan) | ja-JP | true | true |
Microsoft Haruka - Japanese (Japan) | ja-JP | true | false |
Microsoft Ichiro - Japanese (Japan) | ja-JP | true | false |
Chrome
Chrome 69 for Mac
OSが提供する声に加えて、
name | lang | localService | default |
---|---|---|---|
Google 日本語 | ja-JP | false | false |
Google US English | en-US | false | false |
Google UK English Female | en-GB | false | false |
Google UK English Male | en-GB | false | false |
- Windows の Microsoft Edge では Windowsの声、
- macOS の Safari では macOS の声、
- Google Chrome では macOS + Google の声 / Windows + Google の声、
- Firefox と Opera では OS (macOS / Windows) の声のみが使えました。
細かいところ
SpeechSynthesis
プロパティ
名前 | 型 | 意味 |
---|---|---|
paused |
boolean | 一時停止中か |
pending |
boolean | 発言キューにまだ再生されていない発言が残っているか |
speaking |
boolean | 再生中か |
イベントハンドラ
onvoiceschanged
getVoices()
で返されるオブジェクトに変化があった。
メソッド
名前 | 戻り値 | 意味 |
---|---|---|
cancel() |
void | 停止して、発言キューを空に |
pause() |
void | 一時停止して、発言キューを保持 |
resume() |
void | 一時停止から再生状態に復帰 |
speak(uttr) |
void | 発言キューに発言を追加 uttr: SpeechSynthesisUtterance
|
SpeechSynthesisUtterance
コンストラクタ
SpeechSynthesisUtterance()
SpeechSynthesisUtterance("発言する文章")
プロパティ
名前 | 型 | 意味 |
---|---|---|
lang |
String | 言語。BCP 47言語タグ (ja-JP,en-US,en-GB,zh-CN,ko-KR) |
pitch |
float | 声の高さ。 0 〜2 初期値: 1
|
rate |
float | 速度。0.1 〜10 初期値: 1 (2 = 倍速) |
text |
String or SSML | 文章。 |
voice |
SpeechSynthesisVoice | 声の種類。 |
volume |
float | 音量。0 〜1 初期値: 1
|
イベントハンドラ
名前 | 着火時 |
---|---|
onboundary | 単語もしくは文の境界(?)に達した |
onend | 発言が終了した |
onerror | エラーが発生した |
onmark | SSMLの"mark"タグに達したとき |
onpause | 途中で一時停止された |
onresume | 一時停止状態が解除された |
onstart | 再生開始した |
onendが来ない問題
https://qiita.com/XyYaE7gmJtMJDjh/items/38f4c28d2dbe5b7ef9e2
SpeechSynthesisVoice
プロパティ
名前 | 型 | 意味 |
---|---|---|
name | String | 声の名前 |
lang | String | 声の言語 (BCP47言語タグ) |
default | boolean | ブラウザの既定の声か。 |
localService | boolean | ローカル(OS)の声か ↔️リモート(ネット上)の声 |
voiceURI | String | WebサービスのURI |
最後に
最初は、音声認識と音声合成両方を記事にしようと思っていたのですが、
想定以上に長く(TL;DR不可避に)なってしまったので、音声認識はまた来週投稿します。
お楽しみに。
待ちきれない方は、「Web Speech API | JavaScript プログラミング解説 - so-zou.jp」
がとてもよくまとまっているのでおすすめです。
最後に、SafariとEdge様へ:
純正が対応してくれないと困る
参考文献
MDN Web Docs
https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API
W3C Specification
https://w3c.github.io/speech-api/speechapi.html