朝日ネット 技術者ブログ

朝日ネットのエンジニアによるリレーブログ。今、自分が一番気になるテーマで書きます。

Webアプリケーションに声をつける (Web Speech API, Cloud Text-to-Speech API)

はじめに

こんにちは。朝日ネットでWebアプリケーションの開発を行っている tommy です。

前回 まで簡単な音声アシスタントを作っていました。
今回は、オマケとして、前回までの音声アシスタントに声をつけるために Javascript の Speech Synthesis API 及び GCP のサービスの Cloud Text-to-Speech API を利用してみようと思います。

Speech Synthesis API

第一回 で Web Speech API のうち音声認識用のAPIとして Speech Recognition API を利用しました。
Speech Synthesis API は Web Speech API を構成するAPIの1つで、ブラウザで手軽に声を出すことができます。

Can I Use 的には、Speech Synthesis API は IE 以外の主要なブラウザでサポートされています。しかし、ブラウザによって利用できる声が違うほか、OS によっても利用できる声が違います1
また、Chrome ではバージョン55以降15秒以上の文章を話させると、それ以降 API を使うことができなくなってしまうバグがあるみたいです2

window.speechSynthesis.getVoices()3 で利用できる声のリストが取得できます4

var voices = window.speechSynthesis.getVoices();

日本語の声を使って、実際に声を出してみます。
(日本語の声が存在する OS と ブラウザ の組み合わせでのみ動作します。また、IE では Speech Synthesis API がサポートされていないため動きません。)

<!DOCTYPE html>
<html>
    <head></head>
    <body>
        <h1>Web Speech API</h1>
        <input type="button" value="こんにちは" onclick="Hello()">
        <script type="text/javascript">
            function Hello() {
                var utterThis = new SpeechSynthesisUtterance();
                utterThis.text = 'こんにちは';
                var voices = window.speechSynthesis.getVoices();
                for(i = 0; i < voices.length ; i++) {
                    if(voices[i].lang === 'ja-JP') {
                        utterThis.voice = voices[i];
                        break;
                    }
                }
                window.speechSynthesis.speak(utterThis);
            }
        </script>
    </body>
</html>

仕様上、text にプレーンテキストのほかに SSML を指定できるのですが、現在、一部の Voice でしか対応していないようです。

Speech Synthesis API はお手軽なのですが、現時点では環境によって使える声や実装状況が違うのが問題ですね。

Cloud Text-to-Speech API

GCP の Cloud Speech API の逆の機能として、 Cloud Text-to-Speech API が存在します。この他にも音声合成のクラウドサービスはいくつかあり、 AWS には Amazon Polly といったサービスが存在します。
Cloud Text-to-Speech API の使い方は 第三回で利用した Cloud Speech API とほとんど変わりません。API キーを取得したら、text.synthesize に適切な JSON を渡すだけです。

レスポンスとして base64 エンコードされた音声データが返ってくるのでそれを再生します。

hello.go (appengine)

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "text/template"

    "google.golang.org/appengine"
    "google.golang.org/appengine/urlfetch"
)

func main() {
    http.HandleFunc("/", handle)
    appengine.Main()
}

func handle(w http.ResponseWriter, r *http.Request) {
    if r.Method == "GET" {
        w.WriteHeader(http.StatusOK)
        var t = template.Must(template.ParseFiles("synthesis.html"))
        if err := t.Execute(w, nil); err != nil {
            fmt.Println(err.Error())
            return
        }
    } else {
        apikey, err := ioutil.ReadFile("apikey")
        if err != nil {
            fmt.Println(err.Error())
            return
        }
        jsonStr, _ := json.Marshal(map[string]interface{}{
            "input": map[string]string{
                "text": r.FormValue("text"),
            },
            "voice": map[string]string{
                "languageCode": "ja-JP",
            },
            "audioConfig": map[string]string{
                "audioEncoding": "MP3",
            },
        })
        ctx := appengine.NewContext(r)
        client := urlfetch.Client(ctx)
        url := "https://texttospeech.googleapis.com/v1/text:synthesize?key=" + string(apikey)
        resp, err := client.Post(url, "application/json", bytes.NewBuffer(jsonStr))
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        defer resp.Body.Close()
        body, _ := ioutil.ReadAll(resp.Body)
        w.Header().Set("Content-Type", "application/json")
        fmt.Fprintln(w, string(body))
    }
}

synthesis.html

<!DOCTYPE html>
<html>
    <head></head>
    <body>
        <h1>Cloud Text-to-Speech API</h1>
        <form method="POST" onsubmit="return Submit(this);">
            <input type="text" name="text">
            <input type="submit">
        </form>
        <script type="text/javascript">
            function Submit(form) {
                event.preventDefault();
                var oReq = new XMLHttpRequest();
                oReq.open("POST", "synthesise", true);
                oReq.onload = function (oEvent) {
                   new Audio("data:audio/mp3;base64," + JSON.parse(this.responseText).audioContent).play();
                };
                oReq.send(new FormData(form));
                return false;
            }
        </script>
    </body>
</html>

voice.name にパラメータを渡すことで使用する声を変更することもできます5

Dialogflow を利用する場合、beta版 ですが detectIntent にオプションを指定することで、自動で Cloud Text-to-Speech API を使った音声データを返すように指定することもできます[参考]。

おわりに

ブラウザに話す機能を追加すること自体は非常に簡単になってきました。
単純に文章を与えてあげるだけで、読み間違いも少なく6、自然な感じで読んでくれます。
ただし、現状(特に日本語は)、声の種類が少なく、よく耳にする音声合成の声になってしまいます。 今後、いろいろな種類の声が使える様になったり、音声のカスタマイズができるようになることを期待したいです。
そのうちブラウザに歌わせる事も可能になるかもしれませんね!

採用情報

朝日ネットでは新卒採用・キャリア採用を行っております。


  1. 利用できる声は、OSによって使える声 + ブラウザによって使える声 となるようで、同じOSでもバージョンによって使える声が異なります。現時点ではFirefoxにはブラウザによる声が無いようで、Windows7には日本語用の声が無いようなので、この2つの組み合わせだと、Speech Synthesis API は利用可能なのですが、日本語で話すことができません。

  2. [参考]。自分が試してみた感じ、発音中の文章が途中で途切れるだけでなく、そのウィンドウでそれ以降 Speech Synthesis API で声を出すことができなくなってしまいました。(別のウィンドウなら可)

  3. この MDNページの Example で、手軽に使用しているブラウザでの利用可能な声の一覧が見れるので便利です。

  4. 記事執筆時点では Chrome の場合、ページ表示後少し経過しないと声の一覧が取得できませんでした。MDN のサンプルコードでは voiceschanged イベント発生時に一覧を取得しなおすことにより Chrome の場合でも確実に取得するようになっています。

  5. ただし、現在日本語で利用できる声は2つのみみたいです。(サポートされている音声と言語)

  6. 自分が試してみた範囲では、Google の Voice は読み間違いが少なかったですが、Microsoft の Voice はそこそこ読み間違いしました。