デモ
とりあえず3名の声は使えるとわかったので、それら3役でかけあいができるような簡単なスクリプトを書きました。
こちら に公開しています。
README 通りにセットアップを済ませ、
. / playbook- to- voices 台本. csv - p 台本preset. csv - o . / 音声. wav
と実行すると、CSV で書いた台本が音声ファイル(.wav)として入手できます。
下準備
API を利用するためにやや学習コストが発生します。各要素軽くだけ触れておきます。
利用する API
すでに名前が出てきていますが、利用する API は docomo Developer API の音声合成 API エーアイ REST SSML 版 です。
他の API も試してみたのですが、ボイロの声ではなかったので、この記事では上記 API だけを利用します。
docomo developer に登録して API キーを入手
docomoAPI を使うためには API キーが必要です。
会員登録とアプリケーションの利用申請を出して、API キーを入手しておいて下さい。
SSML とはなんぞや
さらっと SSML 版と書きましたが、SSML とはSpeech Synthesis Markup Language の略です。
音声合成のためのマークアップ言語です。
微妙にフォーマットが違いますが、Amazon Echo などでも使用されている仕様だそうです。
— Amazon Echo で「バルス」を実現する - Qiita
詳しくは API を叩くときに解説しますが、
声の種類や話す内容だけではなく、よみがな(ルビ)やイントネーションも操作できる パワフルな言語です。
おそらく作り込めばかなり表現力は増すのですが、イントネーション周りは制御がかなり難しかったです。
マークアップさえ与えればその通りに喋った音声が手に入るので、音声自体の扱いは大して気にすることはありません。
台本を作る
とはいえ、最近素の HTML で愚直なマークアップする機会もなかなか減っていると思います。XML ベースの言語って冗長で面倒くさいですし。
ということで、Excel や Google Spreadsheet などで編集することを想定に、CSV の「台本」を受け取って SSML に変換して音声化 してみます。
台本のフォーマットはこんな感じです。
「1列目はボイス名、2列目は調声プリセット(デフォは空)、3列目は喋る内容」という構成にしました。
すでに嫌な予感 MAX な記述が出てきていますが、字幕と喋っている音声が違う 箇所と、「弦巻マキ」の発音がおかしくてイントネーションを弄った 結果です。
デフォルトだと「小比類巻」みたいな山なりの発音になってしまうので、ツルマキの部分を「うずまき」的な発音に寄せた調声です。
詳しくはマキマキのところで後述します。
ボイロ動画を作るなら、背景やら字幕タイミング、立ち絵プリセットだったり差分プリセットだったりと色々必要になってしまうと思うのですが、今回はシンプルに音声のみ に絞って実装します。
プリセットを作る
調声がかなり難しかったので、デフォ値にこだわらずに調声のプリセットも与えられるようにして、利用者側で細かく調声できるようにします。
調声用のプリセットは以下の通りです
キャラ名、プリセット名、喋るスピード、ピッチ、抑揚、ボリューム の順です。
空の場合はデフォ値を使います。
プリセット名が空の場合は、プリセットなし(デフォルト)の調声を変更します
VOICEROID っぽい声を生成する
では早速 API を利用したいと思います。
台本を SSML に変換
先程の台本を今回利用する API に合わせた SSML に変換すると、このようになります。
実際には改行されてませんが、見にくいのでインデントを整えたのが以下の SSML です。
<?xml version="1.0" encoding="utf-8" ?>
< speak version = " 1.1" >
< voice name = " sumire" >
< prosody rate = " 1.4" pitch = " 1.2" > 皆さんこんにちは、結月ゆかりです</ prosody>
</ voice>
< voice name = " maki" >
< prosody rate = " 1.2" >
< phoneme ph = " ツル’/マ’キ" > 弦巻</ phoneme> マキです
</ prosody>
</ voice>
< voice name = " reina" >
< prosody rate = " 1" > ゆっくり霊夢です</ prosody>
</ voice>
...略...
</ speak>
長いので省略しました。
お察しの通りさほど複雑ではないので、SSML を生成するロジック自体はgist の方を見ていただければと思います。
記事では SSML で使うタグの説明にとどめます。
ボイロ化に最低限必要なのは、これらのタグでした。
タグ名
説明
speak
ルート要素。version="1.1"が必要
voice
声の種類を指定する。指定可能な値は後述
prosody
日本語だと韻律 というそう。ピッチや抑揚、スピードを制御できるので調声するために必須
phoneme
日本語だと音素 というそう。その言葉に対する発音の仕方を定義できます。イントネーションを変えたい場合に使用可能
voiceの name 属性に与えられる値のうち、ボイロ製品に該当するのは
属性名
ボイロ名
sumire
結月ゆかり
maki
弦巻マキ
anzu
月詠アイ
です。
デフォルトだと速度やピッチにやや違和感があるので、調声が必要です。
大まかな調声に使うprosodyに指定できる属性は、
pitch(ピッチ)
rate(喋る速度)
range(抑揚)
volume(音量)
です。
これらを調整するだけでかなりそれっぽくなります。詳しくは公式の API ドキュメント を読んで下さい。
phonemeの ph 属性にはJEITA カナ という仕様にもとづいた値が指定可能です。
これがめちゃくちゃ難しい 。何が難しいかって、ドキュメントを読み解くのに一苦労で、なおかつ pdf に書かれている仕様が 100%はカバーされていないようで、何が使えて何が使えないのかがわからない。
完全に手探りで、欲しいイントネーションを探り当てる必要があるので、よほど気になる発音でない限りは触れないほうが無難だと思います。
音声合成 API を叩く
SSML が作れたら、API を叩きます。
API を叩くのは、よくある POST リクエストです。詳細は公式の API ドキュメント に記載があります。
リクエストボディには先程生成した SSML を与えます。
const querystring = require ( 'querystring' )
const fetch = require ( 'isomorphic-fetch' )
const textToSpeech = async ssml => {
const ENDPOINT = 'https://api.apigw.smt.docomo.ne.jp/aiTalk/v1/textToSpeech'
const query = querystring. stringify ( {
APIKEY : process. env. DOCOMO_API_KEY ,
} )
return fetch ( ` ${ ENDPOINT } ? ${ query} ` , {
method: 'POST' ,
body: ssml,
headers: {
'Content-Type' : 'application/ssml+xml' ,
Accept: 'audio/L16' ,
} ,
} )
}
Content-Type は SSML なので良いとして、Accept のaudio/L16ってなんでしょう。
音声フォーマットなのですが、これが音声に詳しくない人(私)にとっては曲者だったので説明します
audio/l16(PCM 音源)を wav 形式に変換する
audio/l16(以降 PCM)というのは、16bit のリニア PCM と呼ばれる音声ファイルの形式です。
pcm 単体では扱いにくいので、ffmpeg で.wav に変換してしまいましょう。
幸い pcm のメタデータ詳細は公式の API ドキュメント に記載されているので、ちゃちゃっと変換してしまいます。
const fs = require ( 'fs' )
const Promise = require ( 'bluebird' )
const ffmpeg = require ( 'fluent-ffmpeg' )
const unlink = Promise. promisify ( fs. unlink)
const toWav = async pcmPath => {
const destPath = pcmPath + '.wav'
return new Promise ( ( resolve, reject ) => {
ffmpeg ( )
. input ( pcmPath)
. inputOptions ( [ '-ac 1' , '-ar 16000' ] )
. inputFormat ( 's16be' )
. output ( destPath)
. on ( 'end' , ( ) => {
console. log ( destPath)
unlink ( pcmPath) . then ( resolve)
} )
. on ( 'error' , reject)
. run ( )
} )
}
PCM ファイルについての説明や、変換処理については別途記事を書いてますので、そちらもあわせてご確認下さい。
— ffmpeg で PCM 音源を WAVE 形式に変換するときにハマったこと | WEB EGG
まとめ
以上が主要な処理の内容になります。各処理の詳細はgist をご確認下さい。
機能の制限が厳しく、製品版のボイロには遠く及びませんでしたが、“それっぽい音声”までは迫れたかなと思います。
色々試しがいがありそうなので、今後もちょこちょこ触ってみようと思います。
以降の内容は、各ボイロごとの SSML のおさらいとハマリポイントを記載します。
結月ゆかりボイスを試してみる
再掲ですが、ゆかりんのボイスを SSML に起こすとこのような感じになります。
豚野郎さんの調整に合わせるには、ピッチ(pitch)と喋る速度(rate)を少し上げるとちょうどよい感じになりました。
< voice name = " sumire" >
< prosody rate = " 1.4" pitch = " 1.2" > 皆さんこんにちは、結月ゆかりです</ prosody>
</ voice>
弦巻マキボイスを試してみる
自分の名前の発音だけ曲者でしたが、それ以外は結構いい感じでした。
マキマキも豚野郎さんの調整に合わせるなら速度を少し上げるといい感じでした。
< voice name = " maki" >
< prosody rate = " 1.4" >
< phoneme ph = " ツル’/マ’キ" > 弦巻</ phoneme> マキです
</ prosody>
</ voice>
月読アイボイスを試してみる
ゆっくり霊夢(Softalk)は Web API がなかったので、代わりにアイちゃんに喋ってもらいました。
これは似せるもなにもないので、適当に合わせています。
アイちゃんは声自体の癖が強めなので、どう調声してもだいたいアイちゃんに聞こえると思います。
< voice name = " reina" >
< prosody rate = " 1.4" > ゆっくり霊夢です</ prosody>
</ voice>
さいごに
voice , preset , text
弦巻マキ , セヤナー , グレートエレキファイア
voice , name , rate , pitch , range , volume
弦巻マキ , セヤナー , 0.5 , 2.0 , 2.0 ,
./playbook-to-voices グレートエレキファイア.csv -p グレートエレキファイア_preset.csv -o ./talk.wav
セヤナーしたかった
アイキャッチ画像に使用した立ち絵はこちらからお借りしました。
— 結月ゆかり 動画用素材 / 柚子胡椒 さんのイラスト - ニコニコ静画 (イラスト)
— 弦巻マキ 動画用素材 / 柚子胡椒 さんのイラスト - ニコニコ静画 (イラスト)