PythonのgTTSが遅い
ラズパイ(OSは「Raspbian Stretch」)からPythonで書かれたスクリプトを使ってGoogle Homeをしゃべらせていたのだが、ここ数日、しゃべらなかったり、しゃべりはじめるのに何分もかかったりと、動作がおかしくなっていた。このスクリプトでは、gTTSというモジュールを使っていて、内部でGoogle Text To Speech APIを叩いているのだが、どうやらこのモジュールがうまく動かなくなっているらしい。
試しに以下のようなスクリプト(speech.py)を動作させてみた。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pychromecast
from gtts import gTTS
import time
tts = gTTS(text="ハローワールド", lang='ja')
tts.save('./test.mp3')
mp3url = 'http://192.168.0.XXX:8000/test.mp3';
#IPアドレスで特定する
googleHome = pychromecast.Chromecast('192.168.0.YYY')
if not googleHome.is_idle:
print("Killing current running app")
googleHome.quit_app()
time.sleep(5)
#しゃべらせる
googleHome.wait()
googleHome.media_controller.play_media(mp3url, 'audio/mp3')
googleHome.media_controller.block_until_active()
ラズパイのIPアドレスは「192.168.0.XXX」、そこでウェブサーバーを起動しておき、そのドキュメントルートに、gTTSを使って生成された音声ファイル(test.mp3)を保存し、それをPyChromecastというモジュールを使ってGoogle Home(IPアドレスは「192.168.0.YYY」)にしゃべらせている。
すると、しゃべらないときには、以下のようなエラーを吐いていることがわかった。
Traceback (most recent call last):
File "./speech.py", line 11, in <module>
tts.save('./test.mp3')
File "/usr/local/lib/python3.8/site-packages/gtts/tts.py", line 295, in save
self.write_to_fp(f)
File "/usr/local/lib/python3.8/site-packages/gtts/tts.py", line 251, in write_to_fp
prepared_requests = self._prepare_requests()
File "/usr/local/lib/python3.8/site-packages/gtts/tts.py", line 194, in _prepare_requests
part_tk = self.token.calculate_token(part)
File "/usr/local/lib/python3.8/site-packages/gtts_token/gtts_token.py", line 29, in calculate_token
seed = self._get_token_key()
File "/usr/local/lib/python3.8/site-packages/gtts_token/gtts_token.py", line 58, in _get_token_key
raise ValueError(
ValueError: Unable to find token seed! Did https://translate.google.com change?
なぜだかわからないが、Google Text To Speech APIからGETできるデータに所望のモノ(なにかしらのtoken?)がないらしい。
そこで、gtts_token.pyにちょこっと追記して、retryモジュールを使って、_get_token_key()をリトライさせてみることにした。
from retry import retry #追記
(中略)
@retry((ValueError, TypeError), tries=10, delay=1) #追記
def _get_token_key(self):
if self.token_key is not None:
これでエラーは出なくなったものの、それでもまだ、しゃべり出すのに何分も待たされることがある。
プロファイルを取ってみると、どうやらchardetという文字コード判定のモジュールの処理で時間がかかっているようなのだが、なぜそうなるのか(なぜ短時間で処理が終わることもあれば、何分も要することもあるのか)さっぱりわからない。
pi@raspberrypi:~ $ python3 -m cProfile ./speech.py | sort +1 -r | head
16048373 function calls (16031699 primitive calls) in 123.627 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
3918870 29.563 0.000 29.563 0.000 codingstatemachine.py:66(next_state)
5 27.045 5.409 48.198 9.640 mbcharsetprober.py:61(feed)
1 12.045 12.045 26.122 26.122 sjisprober.py:56(feed)
1088785 7.701 0.000 12.662 0.000 jpcntx.py:143(feed)
1 7.619 7.619 17.055 17.055 eucjpprober.py:56(feed)
3106 5.281 0.002 5.281 0.002 {method 'findall' of 're.Pattern' objects}
14 4.777 0.341 10.035 0.717 sbcharsetprober.py:77(feed)
解決策その1
考えるのがめんどうになって、別のモジュールを使ってみることにした。
ググって、やはりGoogle Text To Speech APIを使っているgoogle_speechというモジュールが公開されているのをみつけ、早速これに置き換えてみると、待たされることはなくなった。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pychromecast
from google_speech import Speech
import time
speech = Speech("ハローワールド", 'ja')
speech.save('./test.mp3')
mp3url = 'http://192.168.0.XXX:8000/test.mp3';
#IPアドレスで特定する
googleHome = pychromecast.Chromecast('192.168.0.YYY')
if not googleHome.is_idle:
print("Killing current running app")
googleHome.quit_app()
time.sleep(5)
#しゃべらせる
googleHome.wait()
googleHome.media_controller.play_media(mp3url, 'audio/mp3')
googleHome.media_controller.block_until_active()
解決策その2
さらに調べていると、この記事の記載から、どうやらモジュールを使うまでもなく、簡単にGoogle Text To Speech APIを叩けるらしい、ということがわかった。2011年に書かれた古い記事なので事情が変わってないか心配だったが、試してみると、あっさり動いてくれた。待たされることもないし、そもそもラズパイ上にmp3ファイルが保存されることもない。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pychromecast
import time
import urllib.parse
s = "ハローワールド"
s_quote = urllib.parse.quote(s)
mp3url = "http://translate.google.com/translate_tts?ie=UTF-8&q="+s_quote+"&tl=ja&client=tw-ob"
#IPアドレスで特定する
googleHome = pychromecast.Chromecast('192.168.0.YYY')
if not googleHome.is_idle:
print("Killing current running app")
googleHome.quit_app()
time.sleep(5)
#しゃべらせる
googleHome.wait()
googleHome.media_controller.play_media(mp3url, 'audio/mp3')
googleHome.media_controller.block_until_active()
続報
後日、gTTSの2.2.1へのアップデートによって問題が解決したことを確認。
関連ページ