先月末だったかにアメリカのどこかあたりで開催されたらしい AWS re:Invent 2016 という催しもので Amazon という会社がまた色々な新サービスのようなものを発表したようだと風の便りに聞きましたが、その中でも色々な意味で衝撃的だったものの1つが、Text to Speech サービスの「Amazon Polly」でした。
Polly の使いかた自体はもう色々なところで書かれているのでここでは詳しくは触れませんが、これ、入力したテキストを読み上げてもらうだけならホントに超お手軽にできるので、僕も早速適当に何か喋らせてみたりして「うわ〜自然に聞こえるぅぅ」なんて無邪気に遊んでいました。
しかし、そのときふと僕の頭に1つの疑問が浮かんだのです。
最近はプレーンテキストであっても emoji を使うことで表現をリッチにすることがもう世界規模で一般的になっていますよね。もはや僕なんてたまに emoji が1つも使われていない LINE が来るだけで、「ああ、なんて冷めた感じのメッセージなんだ…この人はきっと僕のことを大嫌いに違いない…」とか暗澹と泥濘を這うような気持ちになってしまうものです。さて、Polly ちゃんはどうなのでしょう。ちゃんと emoji に込めた感情を拾ってくれるんでしょうか。
ということで、絵文字 / Emoji Advent Calendar 2016 の14日目の投稿です。Polly は絵文字を喋れるか?
Polly に emoji を食べさせる
早速試してみましょう。
"Hello 🙂 Let's go out and have some fun with 🐶"
という emoji 混じりのテキストを指定してみます。
$ aws polly synthesize-speech --text "Hello 🙂 Let's go out and have some fun with 🐶" --voice-id Joanna --output-format mp3 hello-1.mp3
…
…えっ?まじ?普通に喋ったぞ?こいつ emoji を普通に喋っちゃったぞ …!?Polly が emoji を喋ったあああああ…まじか…
そうか…普通に喋ったか…まじか…
ということで、Polly は emoji を喋れます、おわり。
Interlude
…さて、僕はなぜ、Polly に emoji を喋らせようとして、そしてめでたく Polly が emoji を喋ったにも関わらずこんなにテンションが低いのでしょうか。それは、どうせ喋れないだろうから「Polly はやっぱり emoji を喋れなかったか〜しかたない、さてどうやって喋らせてやろうかな」的なところで話題を広げて記事が書けるんじゃないかと目論んでいたからです。なんといきなり喋れてしまいハイ終わり〜となってしまいました。
しかし、僕はここであることに気づいたのです。
Polly「はろー。スマイリーフェイス…」
ぼく「ん?スマイリーフェイス?」
そうです。GitHub や Slack で 🙂 を入力するとき、私たちは :slightly_smiling_face:
とキーワードを入力します。そうなのです。この emoji は私たちにとって「slightly」に「smiling」な表現を描き出したものなのです。slightly がなかったとしたら、いったいどうやって私がこの emoji に込めた心の機微をあなたに伝えればよいのでしょうか。そう、私たちの手に「slightly」を取り戻さねばならないのです。
ああよかった、記事の続きが書けそうだ。
emojis lexicon で slightly を取り戻す
ということで Polly が 🙂 を見たら「slightly smiling face」と喋ってくれるようにします。
Polly で語彙を増やすためには lexicon という XML のフォーマットを利用します。といってもこれは Polly 独自のものではなく、PLS (Pronunciation Lexicon Specification) として仕様が策定され広く利用されているもの (らしい) です。
Polly での lexicon の利用については詳しくは公式のドキュメントを読めばすぐわかりますが、用意した lexicon ファイルに対して AWS CLI 経由で
polly put-lexicon -name [lexicon名] --content [ファイル URI]
polly delete-lexcon -name [lexicon名]
とすることでそれぞれ lexicon の追加と削除が行えます。
ここでやりたいのは 🙂 の読みを「slightly smiling face」とするために lexicon を使うことですので、以下のような PLS ファイルを用意します。
emojis.pls
<?xml version="1.0" encoding="UTF-8"?>
<lexicon version="1.0"
xmlns="http://www.w3.org/2005/01/pronunciation-lexicon"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.w3.org/2005/01/pronunciation-lexicon
http://www.w3.org/TR/2007/CR-pronunciation-lexicon-20071212/pls.xsd"
alphabet="ipa"
xml:lang="en-US">
<lexeme>
<grapheme>🙂</grapheme>
<alias>slightly smiling face</alias>
</lexeme>
<lexeme>
<grapheme>🐶</grapheme>
<alias>wang chang</alias>
</lexeme>
</lexicon>
そして、これを Polly にアップロードします。
$ aws polly put-lexicon --name emojis --content file://emojis.pls
そして、その lexicon を使うように指定してもう一度さっきのテキストを与えてみましょう。
$ aws polly synthesize-speech --text "Hello 🙂 Let's go out and have some fun with 🐶" --voice-id Joanna --output-format mp3 hello-2.mp3 --lexicon-names="emojis"
おお!きました!!文法的に不自然なのが原因か発音もちょっと不自然感が出てしまいましたが、間違いなく期待通りに🙂 を「slightly smiling face」と発音してくれてますね!やったあ!
emojilib のバインディングを作って slightly を取り戻す
しかし、今回は 🙂 の発音だけを話題にしてきましたが、もしかしたら他にも同じように期待と違うように発音されてしまう emoji があるかもしれません。そのためにすべての emoji について調べて lexicon を事前に用意しておかなければいけないのでしょうか。そんなのめんどくさくてやってられませんよね!めんどうなのはいやだ!
「めんどうなのはいやだ!」そう絶叫しながら、うん、なぜだろう。僕はもっとめんどくさい作業に着手しようとしていた。Polly にシェルからさくさくっとテキストを渡せるなら、直接 emoji を食わせるんじゃなくて、そのテキストを何か別の処理のコマンドでラップしてやってそこで変換かけてやればいいんじゃない?
この Advent Calendar の1日目の投稿で、emojilib という JSON のライブラリが紹介されていました。これを使えば、定義されている変換テーブルを利用して、
🙂 => slightly smiling face
と変換するような外部処理を行えば、きっと emoji の発音を自在にコントロールしながら、期待に沿うように Polly を喋らせることができるでしょう。
ぶっちゃけ別にこれを実現したいだけならバインディングは必要なくて、Node で JSON をそのまま読み込むか、がんばれば jq でだって実現できそうなのですが、まーせっかくだしバイナリのポン置きで導入できるようなものにした方がいいかな、ってことでバインディングを用意する方向にします。
で、実は、emojilib の Go バインディングはすでに作っている人がいました 。これは Go 製なので導入の簡単さなど関してはだいぶ要望を満たしてくれるものなのですが、ただ、今回やりたいのは普通やりたいことと逆、つまり、
- emoji をキーワードに変換する
という結構特殊な処理なので、このバインディングはそれを手軽にやれるものではありません。
Crystal 製の emojilib バインディング
ということで、Crystal を使って emojilib のバインディングライブラリである emojilib.cr を作りました。
これを使うと、
$ emojicr -r 🐹
:hamster:
のように CLI で「emoji => キーワード」の変換ができます。
もちろん逆、というか「普通な」使い方もできます。
$ emojicr I :green_heart: You :exclamation:
I 💚 You ❗
ただ、emojilib.cr の実装は結構課題が残っていて、例えば Crystal は Go と同様にワンバイナリであるために実行ファイルに外部リソースのファイルをそのまま仕込むことができないのですが、これをうまい具合に解決できずに「Crystal のプログラムを生成するための Crystal のプログラムがある」という、まー文字通りのメタプログラミングというかそういうことをする必要がありました。これは前述の Go バインディングと同様のアプローチなのですが、Crystal にはマクロがあるのでどうにかもっとうまくできないものかなー、という感じではあります。
emojilib.cr を使って変換した文字列を Polly に渡す
とはいえ、これで入力する emoji をラップすることができるようになったので試してみましょう。
$ aws polly synthesize-speech --text "$(emojicr -r "Hello 🙂 Let's go out and have some fun with 🐶")" --voice-id Joanna --output-format mp3 hello-3.mp3
おお…これはなんというか、そのまんま slightly_smiling
と言ってるからなんか違う感はある (emojilib だと face
もないんですね) けど、今回は「slightly」を喋らせるのが目的だったし、コマンド自体は正常に動いているから良しとするか…良しとしました!
というわけで
改めて。「Polly は emoji を喋れるか?」「はい、もちろん喋れますとも!」
※ちなみに、今確認したのですが、日本語ボイスの「Mizuki」を指定したら emoji を喋ってくれませんでした…emoji は華麗にスルーされた…