Help us understand the problem. What is going on with this article?

Python 3 から Google Home に喋らせる(低遅延)

More than 1 year has passed since last update.

はじめに

Google Home にプッシュで喋らせる場合、google-home-notifier がよく使われると思います。
しかし、なんか大げさな気がする・・・

軽い方法として、Python で pychromecast を使ってしゃべらせる方法があります。

しかし使ってみたら、スクリプトを起動してから喋るまで10秒近くかかっちゃいます。

そこで、できるだけ早く喋り始めさせる方法を模索しました。

pychromecast のキホン

pychromecast は、音声データのある適当なURLをぶち込むと、それを再生してくれます。(それ以外にもいろいろできますが、ここでの使い方はそれを想定しています)

音声データはどこかのWebサーバに置いておく必要があります。任意の言葉をしゃべらせる場合は、テキスト→音声変換が別途必要になりますね。

喋らせる内容が固定であれば、固定の音声ファイルをWebサーバに置いておけばよいです。

ここでは、 http://192.168.0.100/test.mp3 に音声ファイルがある前提で進めます。(Google Home から見える場所であれば、ローカルネットワーク内でもインターネット上でもどちらでもいいです。)

基本的な使い方は以下のようになります。

ghspeech1.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import pychromecast

mp3url = 'http://192.168.0.100/test.mp3';

#Chromecastデバイス(Google Homeも)を探す
chromecasts = pychromecast.get_chromecasts()

if len(chromecasts) == 0:
    print("Google Homeが見つかりませんω")
    exit()

#固定で1個目を使う
googleHome = chromecasts[0]

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()

Google Home が複数台ある場合はどうなるでしょうか?

たまたま最初に見つかったGoogle Homeがしゃべります。
仮にGoogle Homeが1台だけでも、Chromecastが存在する場合はそっちが選ばれてしまう可能性があります。

ほとんどのご家庭では実用になりませんね!!!

で、次。名前でGoogle Homeを特定する方法。

ghspeech2.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import pychromecast

mp3url = 'http://192.168.0.100/test.mp3';

#Chromecastデバイス(Google Homeも)を探す
chromecasts = pychromecast.get_chromecasts()

if len(chromecasts) == 0:
    print("Google Homeが見つかりませんω")
    exit()

#名前で探す
googleHome = next(cc for cc in chromecasts if cc.device.friendly_name == 'リビング')

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()

違いは

#固定で1個目を使う
googleHome = chromecasts[0]

#名前で探す
googleHome = next(cc for cc in chromecasts if cc.device.friendly_name == 'リビング')

になっただけです。
簡単ですね。

ちなみに、モデル名を使って

#モデル名で探す
googleHome = next(cc for cc in chromecasts if cc.device.model_name == 'Google Home Mini')

のように探すこともできます。

ただこれらの方法、しゃべり始めるまでに10秒ぐらいかかります。

どこで時間がかかっているかというと、pychromecast.get_chromecasts() のところです。つまり、デバイス一覧を作るところです。pychromecast のソースを見てみたところ、一覧作成時に5秒の待ちがあります。

一覧の作成は飛ばしたい。

IPアドレス指定でやる

IPアドレス指定でイケる方法が絶対あるはずだ。そう思って調べた結果が以下のソースになります。

ghspeech4.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import pychromecast

mp3url = 'http://192.168.0.100/test.mp3';

#IPアドレスで特定する
googleHome = pychromecast.Chromecast('192.168.0.22')

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()

これだと割とすぐ喋ってくれます。

ちなみに、ubuntu 18.04 で apt install python3-pychromecast でインスコした pychromecast ではエラーが出てしまいます。pip で最新をインスコしましょう。

これで低遅延で喋らせることができましたが、IPアドレスを直接指定するのは嫌だ! やっぱり名前で指定したい!!

一覧の作成はせず、名前で指定する

名前指定で探して、見つかったら可及的速やかにそいつに喋らせる。不可能ではないはずだ。そう思って5時間ぐらいかけて作ったコードが以下です。

ghspeech5.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import pychromecast
from threading import Event
from zeroconf import ServiceBrowser, Zeroconf

googleHomeName = 'リビング'
mp3url = 'http://192.168.0.100/test.mp3';

class MyListener(object):
    def add_service(self, zeroconf, type, name):
        global ip
        info = zeroconf.get_service_info(type, name)
        if info.properties[b'fn'].decode() == googleHomeName :
           #bytesのIPアドレスを xxx.xxx.xxx.xxx に変換
           ip = '.'.join(str(c) for c in info.address)
           #見つかったことを通知
           discover_complete.set() 

discover_complete = Event() #待ち合わせ用
zeroconf = Zeroconf()
listener = MyListener()

# GoogleHomeを探す(非同期実行)
browser = ServiceBrowser(zeroconf, "_googlecast._tcp.local.", listener)

# 見つかるかタイムアウトするまで待ち
discover_complete.wait(5)

googleHome = pychromecast.Chromecast(ip)

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()

zeroconfを使って探索を開始し、指定された名前のが見つかったらすぐにそいつに喋らせます。

探索は非同期実行なので、Event の set() wait() を使っているところがミソです。

まとめ

  • pychromecast を普通に使うとデバイス一覧作成で時間がかかる
  • IPアドレス直指定だと低遅延で喋らせることができる
  • さらに、zeroconfと組み合わせて、名前指定でも低遅延で喋らせることができる!
rukihena
自称フルスタックIoTエンジニア
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした