目的
Pythonとsocket通信を用いて棒読みちゃんにメッセージを読み上げさせる。
環境
- python3.6
- 棒読みちゃんVer0.1.11.0 β21
- Windows10 20H2
後述の「Pythonでコード作成」にはPythonの古いバージョンだと実装されていないメソッドがあるため注意。
まず付属のサンプルソースを読み解く
2021年6月10日現在の最新の棒読みちゃんVer0.1.11.0 β21の配布ファイルの中には、socket通信で読み上げ指示を送るサンプルソースが付属している。内容は再配布禁止となっているため載せられないが、誰でもダウンロードできるので確認してほしい。
サンプルソースはC++版だが、socketさえ使えれば他の言語からでも読み上げ指示を送れるはずだ。
ありがたいことにPythonには標準ライブラリとしてsocketモジュールが付属している。
データのフォーマット
読み上げ指示に使われるデータのフォーマットは付属ファイルに記述されている。
大きく分けて2つのパートからなるようだ。
- 15Byte固定のヘッダー部
- 可変長のメッセージ部
ヘッダー部のフォーマット
ヘッダー部はひと続きで15Byte固定だ。
サンプルソースを見るに、それぞれの要素は符号付き整数(2の補数を使う)と思われる。バイトオーダーはリトルエンディアンのようだ。
順番 | 名前 | 大きさ | 値 |
---|---|---|---|
1 | コマンド | 2Byte | 読み上げ指示では1固定 |
2 | 読み上げ速度 | 2Byte | 50~300, または-1で現在の設定値 |
3 | 読み上げ音程 | 2Byte | 50~200, または-1で現在の設定値 |
4 | 音量 | 2Byte | 0~100, または-1で現在の設定値 |
5 | 声質 | 2Byte | 0で現在の設定値, 1~8でAquesTalk, 10001以上でSAPI5 |
6 | メッセージ部の文字コード | 1Byte | 0でUTF-8, 1でUnicode, 2でShift-JIS |
7 | メッセージ部の長さ | 4Byte | 付属ファイルには書かれていないがサンプルを見るに0~2147483647だろう(signed intの最大値) |
###メッセージ部のフォーマット
メッセージ部はヘッダー部の直後に現れる可変長のデータだ。ここに読み上げさせたいメッセージを格納する。
文字コードはヘッダー部の6番で指定する。
メッセージの長さはヘッダー部の7番で指定する。単位はByteなので注意。
読み上げ指示の流れ
付属のサンプルソースでは
- socketを接続
- ヘッダ部を送信
- メッセージ部を送信
- socketを閉じる
という流れで通信していた。
読み上げ指示のみならデータを送信するだけで、何かを受信する必要はないようだ。
Pythonでコード作成
下記のようなコードを作成した。
実行すると変数MESSAGE
の内容を読み上げてくれた。
BOUYOMICHAN_IP = '127.0.0.1'
BOUYOMICHAN_PORT = 50001
MESSAGE = 'ゆっくりしていってね'
import socket
def make_header(command=1, speed=-1, tone=-1, volume=-1, voice=0, char_code=0, msg_length=0):
"""
棒読みちゃんへの読み上げ指示に使うヘッダー部を生成します。
長さ15のbytes型オブジェクトが返されます。
"""
buffer = bytes()
buffer += command.to_bytes(length = 2, byteorder = 'little', signed = True)
buffer += speed.to_bytes(length = 2, byteorder = 'little', signed = True)
buffer += tone.to_bytes(length = 2, byteorder = 'little', signed = True)
buffer += volume.to_bytes(length = 2, byteorder = 'little', signed = True)
buffer += voice.to_bytes(length = 2, byteorder = 'little', signed = True)
buffer += char_code.to_bytes(length = 1, byteorder = 'little', signed = True)
buffer += msg_length.to_bytes(length = 4, byteorder = 'little', signed = True)
return buffer
if __name__ == '__main__':
message = MESSAGE.encode('utf-8') #文字コードをutf-8としてbytesオブジェクトに変換する
header = make_header(msg_length = len(message)) #ヘッダー部を生成
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #socket生成
s.connect((BOUYOMICHAN_IP, BOUYOMICHAN_PORT)) #socket接続
s.sendall(header) #ヘッダー部送信
s.sendall(message) #メッセージ部送信
s.close() #socketを閉じる
str型のオブジェクトMESSAGE
をbytes型のオブジェクトmessage
に変換している点に注意してほしい。
これはヘッダー部を生成する際にlen(message)
で文字数ではなくバイト数を得るためだ。
>>> message = "テスト" #これはstr型
>>> bytes_message = message.encode('utf-8') #これはbytes型
>>> print(len(message)) #3文字
3
>>> print(len(bytes_message)) #9バイト
9
他にも試して分かったこと
- 読み上げ指示は1件ごとにsocketを閉じなければならない。つまり1度の接続で複数の読み上げ指示を送ることはできない。複数読み上げさせたいときはその都度socketを生成し直そう。
- ヘッダー部のコマンドの値を変更することで他にも色々な指示が送れる。詳しくは棒読みちゃん付属のサンプルを参照。
展望
Pythonから読み上げ指示を送ることができた。つまりPythonの豊富なライブラリ群を使って色々なものを読み上げさせられるということだ。夢が広がる。
また、socket通信を用いることによってリモートマシンに読み上げ指示を送ることができる。これはローカル専用のHTTP連携やIpcClientChannel連携にはない利点だ。使い所は思いつかないが...。
とりあえず筆者はfeedparserモジュールとrequestsモジュールを使ってニュースサイトのRSSと記事を読み上げさせたいと思っている。