Python
VOICEROID2
VOICEROID

VOICEROID2(紲星あかり)をプログラムから動かしてみる

やりたいこと

コマンドラインとかからVOICEROID2に話してほしい言葉を与えて話してくれる仕組みはない(手動入力しないと話してくれないのは頂けないよね・・・)
       ↓
何かのアクションがあった場合に自動で話してほしい
例)Twitterで返信されたら内容を話してくれる
  新刊の発売があったら声で教えてほしい

やったこと

例に挙げた「Twitterで返信されたら内容を話してくれる」をやってみた。
フロー.png
図のようにTwitter側からのメンションのデータ検知、送信をIFTTTの連携で処理し、BeebottleにPOSTする。POSTデータはPythonで監視し、POSTデータがあったらVOICEROID2を操作し、話させる処理を行う。

IFTTTの連携側~BeebotteへのMQTTPublishとPythonでの監視

ググったら出てきます。
IFTTT から Raspberry Pi に指示を出す(@minatomirai21氏)
IFTTTとBeebotteを使ってGoogleHomeからRaspberryPiを操作する(@msquare33氏)
などが参考になりました。

PythonからVOICEROID2を操作する方法?

初めはVOICEROID2のウィンドウハンドルを取得し、その子ウィンドウハンドルにあるだろうテキストエリアの箇所にテキストを入れようと思っていました。
(参考:すまーときりたん (Google Homeときりたんが家電を制御するよ!)(@spicapaula氏)
ですが、VOICEROID EX+では上手くいくらしいですが、VOICEROID2では上手くいきません。
というのも子ウィンドウハンドルが見つかりません・・・(spy++でみてもなにもない・・・)
spy++.png

なので別の方法を模索。
UI Automationならできるという記事がありました。
VOICEROID2をコマンドラインから操作するための簡単なサンプルソース - 努力したWiki
こちらの記事はC#なのですが、今回はPythonでやります。

PythonでUI Automation

ここからコードのお話です。(ここが一番時間かかりました)
PythonでのUI Automationの記事はほとんどないので手探り状態で進めます。幸いUI Automationできそうなモジュールが見つかったので利用します。

pywinautoの導入

pywinautoのサイト
pipで入れます

pip3 install pywinauto

VOICEROID2を操作

イメージはウィンドウハンドルの子要素を探すのと同じ感じで
デスクトップのエレメント→VOICEROID2のエレメント(デスクトップの子エレメント)→テキストエリアのエレメント(VOICEROID2の子エレメント) の順でエレメントを取得して操作していきます

VoiceRoid2_Automation.py
# -*- coding: utf-8 -*-
import pywinauto

def search_child_byclassname(class_name, uiaElementInfo, target_all = False):
    target = []
    # 全ての子要素検索
    for childElement in uiaElementInfo.children():
        # ClassNameの一致確認
        if childElement.class_name == class_name:
            if target_all == False:
                return childElement
            else:
                target.append(childElement)
    if target_all == False:
        # 無かったらFalse
        return False
    else:
        return target


def search_child_byname(name, uiaElementInfo):
    # 全ての子要素検索
    for childElement in uiaElementInfo.children():
        # Nameの一致確認
        if childElement.name == name:
            return childElement
    # 無かったらFalse
    return False

def talkVOICEROID2(speakPhrase):
    # デスクトップのエレメント
    parentUIAElement = pywinauto.uia_element_info.UIAElementInfo()
    # voiceroidを捜索する
    voiceroid2 = search_child_byname("VOICEROID2",parentUIAElement)
    # *がついている場合
    if voiceroid2 == False:
        voiceroid2 = search_child_byname("VOICEROID2*",parentUIAElement)

    # テキスト要素のElementInfoを取得
    TextEditViewEle = search_child_byclassname("TextEditView",voiceroid2)
    textBoxEle      = search_child_byclassname("TextBox",TextEditViewEle)

    # コントロール取得
    textBoxEditControl = pywinauto.controls.uia_controls.EditWrapper(textBoxEle)

    # テキスト登録
    textBoxEditControl.set_edit_text(speakPhrase)


    # ボタン取得
    buttonsEle = search_child_byclassname("Button",TextEditViewEle,target_all = True)
    # 再生ボタンを探す
    playButtonEle = ""
    for buttonEle in buttonsEle:
        # テキストブロックを捜索
        textBlockEle = search_child_byclassname("TextBlock",buttonEle)
        if textBlockEle.name == "再生":
            playButtonEle = buttonEle
            break

    # ボタンコントロール取得
    playButtonControl = pywinauto.controls.uia_controls.ButtonWrapper(playButtonEle)

    # 再生ボタン押下
    playButtonControl.click()

実際の動きはtalkVOICEROID2関数を見てもらえればわかります。
子要素を辿って該当エレメントを取得し、操作します。(エレメントがわかりづらいのでInspectツールを使うのをお勧めします)
テキストのエレメント取得→話したい言葉を入れる→再生ボタンエレメント取得→ボタン押下 で処理します。

これで、任意の言葉を話してくれるようになったので、Beebotteの監視と連携させてOKです。

VOICEROID2とbeebotte監視の連携

beebotteのメッセージ受信時の処理にVOICEROID2の処理を入れればOKです。

Twitter_Notification.py
import paho.mqtt.client as mqtt
import json
from VoiceRoid2_Automate import talkVOICEROID2
import time


TOKEN = "XXXXXXXXXXXXXXXXXXXX"
HOSTNAME = "mqtt.beebotte.com"
PORT = XXXX
TOPIC = "XXXX/XXXX"
CACERT = "mqtt.beebotte.com.pem"

def on_connect(client, userdata, flags, respons_code):
    client.subscribe(TOPIC)

def on_message(client, userdata, msg):
    print (json.loads(msg.payload.decode("utf-8"))["data"])
    data = json.loads(msg.payload.decode("utf-8"))["data"]
    for i in data:
        talkPhrase = "{0}さんからメンションです。\n{1}".format(i["from"],i["text"])
        talkVOICEROID2(talkPhrase)
        print ('sleep:{0}s'.format(len(talkPhrase)/5))
        time.sleep(len(talkPhrase)/5)
        print('sleep_end')


client = mqtt.Client()
client.username_pw_set("token:%s"%TOKEN)
client.on_connect = on_connect
client.on_message = on_message
client.tls_set(CACERT)
client.connect(HOSTNAME, port=PORT, keepalive=60)
client.loop_forever()

言葉を発するのに時間がかかり、発話中に動かそうとすると異常終了してしまうので、ある程度ウェイトする必要があります・・・

最後に

じつはこれにはいくつか問題があります

  • 改行に対応できない(webhooksとBeebotteの連携時の問題)
  • 数時間後にしか話してくれない(TwitterとIFTTTの連携の問題)
  • VOICEROID2のウィンドウが必ずアクティブになる(コードの問題)

上二つはPCからTwitterを見れば解決するので、どうにかしたいなあと。
最後のはちょっと解決策がわからないかも・・・って感じです。。。
何かご存知の方いらっしゃったら教えていただければ嬉しいです。。。