Pythonプログラムのcattコマンドを高速化する


cattとは

cattはPythonで書かれたプログラムで、指定したURLの音楽・動画・WebページをGoogle Chromecast/Home/Nest Hubで再生・表示させることができます。
Raspberry Piからcattコマンドを実行することで、Google Homeに音声メッセージを再生させたり、Nest Hubに好きなWebページを表示させたりできるので、DIYスマートホームシステムを作るのに役立ちます。

cattの問題点 - 起動が遅い

高機能なのですが、起動がかなりもっさりしています。ボトルネックになっているのはPhtonモジュールのimportで、以下のようにRaspberry Pi4でも一秒以上。Raspberry Pi3では2秒弱の時間がかかっています。通常の動画再生ならそれほど気になりませんが、スマートホーム用に使うとなると1秒でもレスポンスを早くしたいところです。

PYTHONPROFILEIMPORTTIME=1 catt -d 192.168.x.xxx cast_site https://www.google.com
:
import time:      1652 |     795904 |       youtube_dl
import time:       976 |     796880 |     catt.stream_info
import time:      2753 |    1213944 |   catt.controllers
import time:      1570 |       1570 |   catt.http_server
import time:       711 |        711 |   catt.subs_info
import time:      6028 |    1296239 | catt.cli

高速化の方法

cattコマンド相当のサービスをPythonで作成する

importでの起動待ち時間の影響をなくすために、catt相当のサービス(Pythonで記述)をあらかじめ立ち上げておくことにします。コマンドラインのオプション処理を自前で行う必要があるかと思ったのですが、click.testing.CliRunnerというテスト用のメソッドを使って、catt本来のコマンドラインオプション処理を流用することができました。

以下が作成したサービスプログラムです。port 50007に、cattコマンドと同じコマンドラインパラメータを送信すると処理してくれます。

なお、終了時に子プロセスを終了させるため、psutilモジュールを使用しています。pip3 install psutilでインストールをお願いします。

~/pi/bin/catt_service.py
#!/usr/bin/python3                                                                                                   
# -*- coding: utf-8 -*-                                                                                              
# catt_service : Service to launch catt                                                                              

import os
import socket
import signal
import sys
import syslog
import traceback

import psutil

from click.testing import CliRunner
from catt.cli import get_config_as_dict
from catt.cli import cli

def onexit(signum, stack):
    syslog.syslog('stopped')
    for child in psutil.Process(os.getpid()).children():
        child.kill()
    sys.exit()

HOST = ''                 # Symbolic name meaning all available interfaces                                           
PORT = 50007              # Arbitrary non-privileged port                                                            

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen(5)

syslog.syslog('started. port=' + str(PORT))

while True:
    conn, addr = s.accept()
    syslog.syslog('connected from' + str(addr))

    pid = os.fork()

    if pid != 0:
        signal.signal(signal.SIGHUP, onexit)
        signal.signal(signal.SIGINT, onexit)
        signal.signal(signal.SIGQUIT, onexit)
        signal.signal(signal.SIGTERM, onexit)
    else:
        try:
            #conn.setTimeout(3)                                                                                      
            data = conn.recv(1024)
            conn.close()

            if data:
                syslog.syslog('args: ' + data.decode('utf-8'))
                #args = data.decode('utf-8').split()                                                                 
                runner = CliRunner()
                result = runner.invoke(cli, data.decode('utf-8'), obj=get_config_as_dict())
                syslog.syslog('catt exit code {0:d}, output: {1}'.format(result.exit_code, result.output))
            else:
                pass# syslog.syslog(syslog.LOG_INFO, 'no data received')                                             

            sys.exit()

        except Exception as e:
            syslog.syslog(syslog.LOG_ERR, 'exception:' + traceback.format_exc())
            conn.close()
            sys.exit()

サービスの登録と実行

  • serviceファイルの作成
/usr/lib/systemd/system/catt.service
[Unit]
Description=Catt - Cast All The Things - Service

[Service]
ExecStart=/home/pi/bin/catt_service.py
KillMode=process
Restart=always

[Install]
WantedBy=multi-user.target
  • catt serviceを有効化する
sudo systemctl daemon-reload
sudo systemctl enable catt.service
sudo systemctl start catt.service
  • catt serviceが実行されていることを確認
sudo systemctl status catt.service

動作確認

  • まずnetcatをインストール
sudo apt-get -y install netcat
  • port 50007 にコマンドライン内容を送信。以下の例ではNest HubにGoogleのホームページを表示させて見ます。
nc localhost 50007
-d <Google Nest HubのIPアドレス> cast_site https://www.google.co.jp

自分の場合、実際にはnode-redのtcp outノードを使ってGoogle Nest HubへWebページの表示リクエストを行ってます。