日記/2020-11-8

最終更新時間:2020年11月08日 10時16分22秒

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へのアップデートによって問題が解決したことを確認。


関連ページ