voicepeak邪神ちゃんとpywin32を使って邪神ちゃんがニコ生のコメントを読み上げるプログラムを作った(前編)

# 初めに
先日、邪神ちゃんドロップキック公式から邪神ちゃんが人工音声ソフトになるという衝撃的ニュースが発表されました。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">【すごいものを作ってしまった】邪神ちゃんが人口音声ソフトになりました！無料版、今すぐダウンロードできます！<a href="https://twitter.com/hashtag/%E9%82%AA%E7%A5%9E%E3%81%A1%E3%82%83%E3%82%93%E3%83%89%E3%83%AD%E3%83%83%E3%83%97%E3%82%AD%E3%83%83%E3%82%AF?src=hash&amp;ref_src=twsrc%5Etfw">#邪神ちゃんドロップキック</a> <a href="https://twitter.com/hashtag/jcdk?src=hash&amp;ref_src=twsrc%5Etfw">#jcdk</a><a href="https://t.co/K7D2XuzalP">https://t.co/K7D2XuzalP</a> <a href="https://t.co/HSvy9RkLvl">pic.twitter.com/HSvy9RkLvl</a></p>&mdash; 【合成音声】邪神ちゃんドロップキック (@jashinchan_PJ) <a href="https://twitter.com/jashinchan_PJ/status/1632580958192037888?ref_src=twsrc%5Etfw">March 6, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
ニコ生で活動している私としては、真っ先に邪神ちゃんコメントを読んでもらいたいという考えが浮かんだのですが、voicepeakにはコメントビューアーと連携して、コメントを読み上げるような機能はついていないようです。そこで今回は次のような作戦でコメントを読んでもらうことに成功しました。

# 今回作ったプログラムの流れ
### 1.pythonでコメントを取得
![スクリーンショット 2023-03-14 221529.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3245561/6682c974-340d-22aa-eaff-2c3fc299914a.png)


### 2.取得したコメントをvoicepeakに自動入力（バックグラウンド）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3245561/6688d999-f3da-14ab-587e-68d9473f396d.png)
普通のPC自動操作ツールを使うと、ツールを使っている間にPCを操作する(もちろん配信も)ことはできなくなるので、今回はバックグラウンドで自動入力を実現する必要がありました。

### 3.音声を再生し、voicepeakに入力した文字を全て消去
こちらの操作ももちろんバックグラウンドで行う必要があります

# 私が作ったコードを参考にしたい方へ
参考にしたい方は以下のソフトウェアをインストールして、この記事を読むと理解が捗ります
## Spy++
win32apiを使ったPC自動操作をするにあたり、かなり重要になってくるツールです。
インストール方法については、こちらをご覧ください

https://r9.hateblo.jp/entry/2018/02/16/092053


## voicepeak邪神ちゃん
実際に、説明と同じ流れで、ソフトを動かしたほうが、分かりやすいと思います。無料なのでぜひインストールしてみてね。

https://t.co/K7D2XuzalP

# 説明につきまして
実はwin32apiについて私が初めて知ったのは、この記事を書く4日前なので、私も未だに良く分からない点が多いのでご了承ください。

# win32apiを使った自動操作について
最初に、win32apiを利用するために以下のコマンドを作業するディレクトリで実行してください
```
python -m pip install --upgrade pywin32
```
インストールできたら、以下のコードを実行してみてください。
(このページで紹介するコードのpythonのバージョンは全て、python 3.11です)
``` MouseMove.py
win32api.SetCursorPos((161, 167))
```
実行するとマウスカーソルが画面左上に飛ばされると思います。
このようにpywin32ではwin32apiを利用することにより、PCを操作することが可能なのですが、なぜこのようなことができるのかを説明したいと思います。

まず、spy++を開いてください。(64bit版を開いてください)
この中からvoicepeakのウインドウを見つけて(ウィンドウ検索をすると楽です。ウインドウ検索の使い方は他のサイトを参照してください。)、左クリックをして、メッセージを開いてください
![スクリーンショット 2023-03-14 231209.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3245561/b9c6eeb8-b31f-3c56-8831-bfd702f47618.png)
メッセージを開き、voicepeakをアクティブ化する(バックグラウンドではなく、フォアグラウンドに持ってくる)と大量のメッセージが流れると思います。
![スクリーンショット 2023-03-14 231457.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3245561/21f36226-9ecc-9aa2-6c46-93ca3d42c639.png)
上記の画像はマウスに関するメッセージが多く流れています。よく見ると、先ほど実行した`SetCursorPos`関数とほぼ同じ名前の***WM_SETCURSOR***というメッセージも流れています。windowsのアプリケーションは、マウスを動かすと自動的に、現在のマウスポインターの座標がメッセージとして送信され、それに応じて処理を行います。この仕様を利用して、マウスを動かさずとも直接ウィンドウにマウスポインターの座標のメッセージを送ってやると、マウスを動かした時と同じ処理を実現することができるという訳です。

# テキストの自動入力
Spy++に戻って、今度は文字入力に関するメッセージだけを取得しましょう。
やり方は簡単で、メッセージが大量に流れる画面で右クリックし、ログオプションをクリックします。
![スクリーンショット 2023-03-14 233044.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3245561/3c2eef16-ddf6-0312-5aeb-5e5bbff48823.png)
そこから、メッセージを開き、メッセージグループのキーボードだけが選択されている状態にします。
![スクリーンショット 2023-03-14 233212.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3245561/fdaefe19-b53a-7738-6cdf-9903f7a819a9.png)
okを押して、voicePeakで何か文字を入力しながら、流れるメッセージを見てみましょう。
![スクリーンショット 2023-03-14 233506.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3245561/8195a8b6-c077-d93c-3602-68af39ffb934.png)
すると、***WM_KEYDOWN***、***WM_CHAR***、***WM_KEYUP***という3つのメッセージが流れたと思います。
今回私は、aという文字を入力したのですが、上の画像にもその様子が見て取られると思います。
上の画像で、***WM_CHAR***メッセージの中に***97***という数字が含まれていますが、これはaのunicode(UTF-16)が96番なので、こうなっています。つまり、この数字を自分の入力したい文字のunicodeにしてしまえばよいわけです。
というわけで以下のプログラムを作ってみました。
``` wmchar.py
import win32api
import win32con
import win32gui
import time
time.sleep(5)    #5秒の猶予を作る
VoicePeak = win32gui.FindWindow(None, "VOICEPEAK")  #voicepeakのウインドウハンドルを取得
win32gui.SendMessage(VoicePeak, win32con.WM_CHAR,ord("邪"),0)  #読み上げたい文字を送信
```
このプログラムを実行して、5秒以内にvoicepeakのテキストを入力するところをクリックし、テキストカーソルが点滅するようにしてください。
すると"邪"という文字が入力されると思います。
`ord('邪')`には邪という文字のunicodeが格納されており、`SendMessage`関数に`win32con.WM_CHAR`と`ord("邪")`を含めることで、メッセージを送っています。`VoicePeak`変数が引数に含まれていますが、これはメッセージを送るウィンドウの宛先情報が含まれています。後ろについている`0`の引数については、今回は使わないので、解説しません。

ちなみに先程登場した、***WM_KEYDOWN***と***WM_KEYUP***メッセージですが、voicepeakの文字入力に関しては、***WM_KEYDOWN***と***WM_KEYUP***はメッセージを送らずとも、入力できてしまいます。これはアプリケーション固有のもので、ソフトを作った人により、***WM_KEYDOWN***と***WM_KEYUP***が必要になったり、いらなかったりするようです。

# バックグラウンドでのテキストの自動入力
現在のプログラムだとバックグラウンドでの入力はできないので、少し手を加えていきます。

前回のプログラムではなぜ、バックグラウンドで動かなかったというのと、実は文字列を送ること自体はバックグラウンドで送れることはできていたのですが、voicepeak側がテキスト入力待ち状態になっていないので、そこを手動でやる必要があったため、バックグラウンドで操作している感じがなかったのです。
というわけで以下の一文を加えました。
``` wmchar.py
win32gui.SendMessage(VoicePeak,win32con.WM_SETFOCUS,0,0) 
```
**WM_SETFOCUS**はテキストエリアを有効化できるメッセージです。
この一文を加えるだけでも、テキストエリアをクリックせずとも入力することはできますが、バックグラウンドでテキストエリアを有効化することはできませんでした。どうやら、ウインドウをアクティブ化しないといけないようです。
``` wmchar.py
win32gui.SendMessage(VoicePeak,win32con.WM_ACTIVATE,2,0)
```
**WM_ACTIVE**はウインドウをアクティブ化するメッセージです。というわけで、このメッセージをvoicepeakウインドウに送ってみたのですが、ウインドウは有効化されませんでした。どうやら、voicepeakウインドウではなく、別のウインドウに送る必要があるようです。というわけで、さらに修正をしてバックグラウンドで自動入力するプログラムはこちらです。
``` autoSendChar.py
#ウインドウハンドルを取得
VoicePeak = win32gui.FindWindow(None, "VOICEPEAK")  #voicepeakのウインドウハンドルを取得
JUCE = win32gui.FindWindow(None,"JUCEWindow")       #voicepeak(juce)のウインドウハンドルを取得
#メッセージを入力
win32gui.SendMessage(JUCE, win32con.WM_ACTIVATE,2,0)    #ウインドウをアクティブ化
win32gui.SendMessage(VoicePeak,win32con.WM_SETFOCUS,0,0)    #テキストエリアにフォーカスする
win32gui.SendMessage(VoicePeak, win32con.WM_CHAR,ord("邪"),0)  #読み上げたい文字を送信
```

# バックグラウンドでの自動再生
![スクリーンショット 2023-03-19 221305.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3245561/11ead69a-cd1f-7d08-9f43-bfeb08737ef5.png)
voicepeakで邪神ちゃんに文字を読んでもらうには、赤丸の再生ボタンを押すと再生できます。このボタンを押すにはマウスカーソルを動かし、クリックするしかないのですが、それだとフォアグラウンドでの作業にも支障がでるので、違う方法で攻めてみます。
調べたところ、voicepeakにはショートカットキーがあり、テキストの入力状態が有効でないときにスペースキーを押すと再生できるようです。
前回のプログラムに追記する形だと、テキストの入力状態が有効化されたままなので、一旦無効化してから、スペースキーを押すプログラムを実行するプログラムにする必要があります。以上の処理を前回のソースコードを追記してみました。
``` autoPlay.py
#ウインドウハンドルを取得
VoicePeak = win32gui.FindWindow(None, "VOICEPEAK")  #voicepeakのウインドウハンドルを取得
JUCE = win32gui.FindWindow(None,"JUCEWindow")       #voicepeak(juce)のウインドウハンドルを取得
#メッセージを入力
win32gui.SendMessage(JUCE, win32con.WM_ACTIVATE,2,0)    #ウインドウをアクティブ化
win32gui.SendMessage(VoicePeak,win32con.WM_SETFOCUS,0,0)    #テキストエリアにフォーカスする
win32gui.SendMessage(VoicePeak, win32con.WM_CHAR,ord("邪"),0)  #読み上げたい文字を送信

###以下が追記箇所
win32gui.SendMessage(VoicePeak,win32con.WM_KILLFOCUS,0,0)   #テキストエリアのフォーカスを外す
time.sleep(0.5)
#再生
win32gui.SendMessage(VoicePeak,win32con.WM_CHAR,32,0)   #スペースを送信して、読み上げを実行
```
スペースキーを押す動作ですが、voicepeakでは**WM_CHAR**を用いて、スペースキーが押されたかを確認しているようです。
ちなみに、スペースキーを押す前に0.5秒待機していますが、これはvoicepeakの音声生成を待機しています。

# 文字列の自動削除
入力した文字列を削除するには、バックスペースキーを押すことで対応します。しかし、カーソル位置が一番左側にあるため、右側に移動させてからバックスペースキー入力を送る必要があります。
``` autoDelete.py
#入力した文字を削除
win32gui.SendMessage(VoicePeak,win32con.WM_SETFOCUS,0,0)    #テキストエリアにフォーカスする
win32gui.SendMessage(VoicePeak,win32con.WM_KEYDOWN,0x27,0)  #右矢印キーを入力

win32gui.SendMessage(VoicePeak,win32con.WM_KEYDOWN,8,0)     #バックスペースを入力
win32gui.SendMessage(VoicePeak,win32con.WM_KILLFOCUS,0,0)
```

# 再生中の待機時間の設定
voicepeakは音声の再生中にテキスト入力をされる(文字列の削除)と音声の再生が中断されてしまいます。
というわけで、音声の再生をしてから、文字列を削除するまで待機させる必要がありますが、待機する秒数の設定が問題です。当たり前ですが、文字列が長ければ長いほど、邪神ちゃんは読み上げるのに時間がかかりますし、文字列が短いと一瞬で読み上げてしまいます。文字列によって、待機時間を変えてあげる必要があるということです。
わたしは、この待機時間の設定ですが、voicepeakの描画に目を付けました。
![スクリーンショット 2023-03-20 000228.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3245561/30b22afb-74a7-1a5f-adbf-35edba434402.png)
音声の再生中はこの部分の秒数が変化します。
Spy++で見ると、
![スクリーンショット 2023-03-20 001040.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3245561/61d66110-960b-4317-e19e-d62ee63d4a15.png)
WM_PAINTというメッセージが音声再生中に流れていることが分かります。
これを取得して、WM_PAINTが流れているうちは待機させるプログラムを作ればよい訳です。

なのですが、ここのプログラム作成は難しく、私の力ではコードを書くことができませんでしたが、この部分のコード作成は視聴者の方にやっていただきました。ありがとうございました。（私は、邪神ちゃん読み上げプログラムの作成を生配信上で行っています。）
ソースコードは少し複雑なので、このページでは見せることができません。もし、見たい方がいらっしゃいましたら、毎日私は配信しているので、コメントで見せてほしい旨を伝えていただければ見せることは可能です。

https://www.nicovideo.jp/user/98746932/

https://www.twitch.tv/boxfish_jp/videos

(同時配信をしているので、どちらかお好きな方でコメントを）

# voicepeak自動化プログラムの完成形
解説し忘れていましたが、一文字ではなく、文字列をテキスト入力するには、一文字ずつ送ることで、文字列を入力することができます。
以上を踏まえて、完成したプログラムはこちらです。
``` sendVoicePeak.py
def sendVoicePeak(message):
    #ウインドウハンドルを取得
    VoicePeak = win32gui.FindWindow(None, "VOICEPEAK")  #voicepeakのウインドウハンドルを取得
    JUCE = win32gui.FindWindow(None,"JUCEWindow")       #voicepeak(juce)のウインドウハンドルを取得
    time.sleep(0.05)
    #メッセージを入力
    win32gui.SendMessage(JUCE, win32con.WM_ACTIVATE,2,0)    #ウインドウをアクティブ化
    win32gui.SendMessage(VoicePeak,win32con.WM_SETFOCUS,0,0)    #テキストエリアにフォーカスする
    for s in message:
        print(s);
        win32gui.SendMessage(VoicePeak, win32con.WM_CHAR,ord(s),0)  #読み上げたい文字を送信
        time.sleep(0.05)
    win32gui.SendMessage(VoicePeak,win32con.WM_KILLFOCUS,0,0)   #テキストエリアのフォーカスを外す

    time.sleep(0.5)
    #再生
    win32gui.SendMessage(VoicePeak,win32con.WM_CHAR,32,0)   #スペースを送信して、読み上げを実行
    nowPlaying();   #音声が再生するまで待機する関数(再生中の待機時間の設定で解説?した部分)

    #入力した文字を削除
    win32gui.SendMessage(VoicePeak,win32con.WM_SETFOCUS,0,0)    #テキストエリアにフォーカスする
    for s in message:
        win32gui.SendMessage(VoicePeak,win32con.WM_KEYDOWN,0x27,0)  #右矢印キーを入力
        win32gui.SendMessage(VoicePeak,win32con.WM_KEYDOWN,8,0)     #バックスペースを入力
        time.sleep(0.05)
    win32gui.SendMessage(VoicePeak,win32con.WM_KILLFOCUS,0,0)

sendVoicePeak("ゆりねもこれでおしまいですの")
```
# 後編へ
コメント取得プログラムとの連携や、こまかい調整などは後編でやっていきたいと思います

https://qiita.com/boxfish_jp/items/01d6d3e3ea0575adc492

# 分からない点はこちらへ
毎日配信しているので、生放送のコメントで教えてもらえると対応がしやすいです。

https://www.nicovideo.jp/user/98746932/

https://www.twitch.tv/boxfish_jp/videos

(同時配信をしているので、どちらかお好きな方でコメントを）