日記/2020-11-8
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へのアップデートによって問題が解決したことを確認。
関連ページ