備忘録的な

プログラミングや機械学習に関する備忘録

Google Cloud Text-to-Speechを使って英語のシャドーイングをする

再来週,国際会議で発表するのですが,私の英語力だと原稿作って暗記していくのが必須です.そこで,合成音声に原稿喋らせて,シャドーイングの練習できないかな?と考えました*1

ちょうど先月,Google Cloud Platform(GCP)でCloud Text-to-Speech(TTS)が公開されました*2.TTSはテキストを音声に変換する技術です.Cloud TTSの特徴はDeepMindが開発したWaveNetという技術が用いられていることで,肉声に近い高品質な音声を生成することができます.
今回はこれを使ってシャドーイング練習用音声を作成します.

ポイントは以下のとおりです.

  1. 原稿を1文1行のテキスト化しておき,一括で1文1音声に変換する
  2. WaveNetを使って高品質な合成音声を生成する
  3. シャドーイングしやすいように,やや発話速度を遅くする
  4. シャドーイングしやすいように,文末に無音を挿入する

準備

GCPやCloud SDK,Cloud TTS APIの設定は公式ドキュメントのとおりに行います.

  1. Quickstart: Text-to-Speech  |  Cloud Text-to-Speech API  |  Google Cloud
  2. Quickstarts  |  Cloud SDK  |  Google Cloud

クライアント側はPythonを使います.準備は以下の通り(Miniconda利用).

pip install --upgrade google-cloud-texttospeech
pip install pydub
conda install -c menpo ffmpeg

音声の生成

基本的には公式ドキュメントの通りにやればよいのですが,シャドーイング練習用に少し修正しています.

話者の変更

voice = texttospeech.types.VoiceSelectionParams(
    language_code='en-US', name='en-US-Wavenet-D')

このページの表からVoice nameにWavenetがついている話者を選択します.6種類のWavenet音声を選択できますが,一通り試してみて話者Dが聞きやすいと感じました.

フォーマットの指定と発話速度の調整

audio_config = texttospeech.types.AudioConfig(
    audio_encoding=texttospeech.enums.AudioEncoding.LINEAR16,
    speaking_rate=0.8)

ファイルフォーマットは,あとで音声ファイルの編集(無音の挿入)を行うためMP3ではなくWAV(LINEAR16)を指定しています.
また,デフォルトの発話速度はシャドーイング練習用としては(私には)早すぎるため,少し遅く(0.8)しています.

文末に無音を挿入

今回,1文1音声ファイルとするのですが,これを音楽プレーヤーで再生すると,音声間のポーズが短く,シャドーイングが(私には)しんどいです.そこで,文末に1秒の無音(ポーズ)を挿入します.音声ファイルの編集はpydubとffmpegというライブラリを使用しています.

with open(path + '.wav', 'wb') as out:
    out.write(response.audio_content)

audio = AudioSegment.from_wav(path + '.wav')
silence =AudioSegment.silent(duration=1000)
audio += silence
audio.export(path + '.mp3', format='mp3')
os.remove(path + '.wav')

Cloud TTSで生成した音声を一度WAVファイルとして保存し,pydubで読み込み,無音を挿入してMP3で保存,最後にWAVファイルの削除という処理になっています.いちいちWAVファイルを生成しなくてもいけるような気もしますが,調べるのが面倒くさかったので...

一括ファイル作成

出力ファイルパス(拡張子無) <Tab> 読ませたい文

という形式のテキストファイルを用意し,以下のように一括で音声を生成します.

with open('scripts.txt', 'r') as fi:
    for line in fi:
        path, text = line.rstrip().split('\t')
        synthesize_text(text, path)

おわり

スクリプト全体は下記のようになりました.
WaveNet,文全体で聞くとまだ不自然だなと思う部分もありますが,単語レベルだと私にはもう肉声と区別がつきません.
これで学会発表練習がんばります.