pythonの処理の処理経過をvoicevoxに非同期的に報告させる

# やったこと
voicevoxを知って「これは仕事や生活に役立つ」と思いました。
そこでpythonのある生活に役立てるべく、一寸コードを書いてみました。

# 動機：処理の進捗を知りたい
何らかの処理をする時に、進捗を知りたいことがあるかと思います。だけど、ターミナルを見てもブワワワワァ！と字が出てくるばかり。ピープ音を出しても何のビープか分かりません。目がシバシバしてきます。
というか、そうじゃなくても…例えばプログレスバーをじっと見るよりは、お茶でも飲みながら声かけられるのを待つほうが優雅じゃないですか！
そこで、僕は喋ってもらうことにしました。

voicevoxというソフトが個人・商用とも無料で使えるようでした。OSSで、しかもhttpサーバーを立ててAPIでやれるみたいです。これは良さそう。
~~というか、組み込まれているずんだもんの声が癖になった~~

# インスコ
下記が公式です。
https://voicevox.hiroshiba.jp/

内容的にはコアとサーバーと全部入り？があるみたい。残念ながらコアは今の所(2022/11/27)は僕はgithubのインストールスクリプトを動かすことが出来ません。
書いてあるとおりにしてもgithubのサーバーが反応しない。ということで、コアを使うのは一旦諦めました。
全部入り版のAppImageの方をダウンロードしてもいいけれど、気取ってエンジン版を入れました。

https://github.com/VOICEVOX/voicevox_engine/releases/latest

エンジンが色々と並んでいます。環境に合うものを入れましょう。
当方Ubuntuゆるふわ老人なので、下記で解凍しました。

```
7z x linux-cpu.7z.001
```

解凍して出来たディレクトリに入って実行権限を与えて実行

```
cd linux-cpu
chmod 755 run
./run
```

pathをはるのは適当に、ね。
これで、ローカルにhttpサーバーが50021ポートに立ちます。
apiのドキュメントもあって、場所は```http://localhost/50021/docs```がデフォルトです。

ちなみに、全部入り版は起動するとサーバーも立つし、色々お試し出来るので、全部入り版で良いんじゃないかなって思います。AppImageが公式で配布されていて、Electron製だそうです。

# 進捗報告用クライアントを実装する
何か言ってもらうのは良いとして、声でレポートして貰う時に役に立つにはいくらか条件があるんじゃないかと思いました。ターミナルのようにログであふれるのは困ります。役立つ条件を書き出してみました。

1. 同時に鳴る音は一つだけ
2. キャッシュを効かせる
3. 非同期的に音を出す

以下、理由です。

<details><summary>理由</summary>

## 1.同時に鳴る音は一つだけ
バックグラウンドでマルチプロセッシングしている場合は複数の声が鳴り響いてわけが分からなくなるかも知れません。まぁ、エヴァンゲリオンみたいに鳴り響くのもかっこいいけど、界隈からすると最早老人[^roujin]である僕には聞き取れないのです👴

[^roujin]: インターネット老人会って程度の老人なので、ガチの高齢者ではない

## 2.キャッシュを効かせる
声を合成するのはそれなりにマシンパワー要るようです。僕は一寸遅延があるなと思いましたが、ノーパソで「一寸の遅延」でもラズパイだったら無視できぬでしょう。僕はたまーにラズパイも使うので、キャッシュによる高速化が必要です🚀

## 3.非同期
同期的に音を出したらば、アナウンスの時間だけ処理が遅くなります。僕はそんなの嫌です🚀

</details>

# 実装
適当に書いていたら意外と長くなったので、githubのurlを貼ります。
内容的にはしょうもないというか、結構単純なのになんでこんなに長くなったんだ…。[^long]

https://github.com/uesseu/ninvoicevox

あとは
```bash
pip install git+https://github.com/uesseu/ninvoicevox
```

[^long]: 一因に要らんurllibのラッパーを書いたからというのがあります。何故かGETをPOSTと言いながら送らないと送れなかったので、結構詰まったのです。結局、ラッパー使って試行錯誤しました。まぁ、単純に下手くそなだけだけど。

# 使い方

pythonで使う場合はこう。
<details><summary>コード</summary>

```python
from ninvoicevox import Speaker, get_speaker_info, AsyncQueue
import time

# 声の情報をゲットする
voice_info = get_speaker_info()
print(voice_info.name)  # voicevoxの声の種類の辞書
print(voice_info.id)  # 上記の逆引き

# 声の設定。ここでは「ずんだもん」の「あまあま」声にする。
# 他にも色々設定があるようだ。
zundamon = Speaker(get_speaker_info().name['ずんだもん']['あまあま'],
                   enable_cache=True)

# ここで、事前に言いたいことをロードしておく。
# デフォルトでバックグラウンドでやってくれる。
# textメソッドでVoiceオブジェクトができる。
start: Voice = zundamon.text('作業を開始しましょう。')
mochi: Voice = zundamon.text('差し入れのずんだ餅をどうぞ。')
nodo: Voice = zundamon.text('ずんだ餅を喉に詰めないでください。')
end: Voice = zundamon.text('作業が終わりました。')

# speakというメソッドで音声を同期的に鳴らす。
nodo.speak()

# 激重タスクがあるとする
def gekiomo():
    for n in range(10):
        print(n)
        time.sleep(0.25)


# このwith文の中でキューの中にputすると、
# 非同期的に声を出してくれるけれど、複数の音声が重ならない。
with AsyncQueue() as aq:
    aq.put(start.speak)
    gekiomo()
    aq.put(mochi.speak)
    gekiomo()
    aq.put(nodo.speak)
    gekiomo()
    aq.put(end.speak)

# 別にwith文必須なわけじゃなくて、start, endメソッドでいい。
aq = AsyncQueue().start()
aq.put(start.speak)
gekiomo()
aq.put(mochi.speak)
gekiomo()
aq.put(nodo.speak)
gekiomo()
aq.put(end.speak)
aq.end()
```
</details>

要約すると、

- get_speaker_info関数で音声を選択する
- Speakerクラスで声を設定する
- Speaker.textメソッドでVoiceオブジェクトを作る
- Voiceオブジェクトは生成されると即座にvoicevoxにバックグラウンドで問い合わせる
- speakメソッドを使うと同期的に音を出す
- 順次バックグラウンド再生したい場合はAsyncQueueを作る
- 一回ずつ喋ってくれる

という感じ。
一応、pipで入れるとninvoiceというコマンドが別途入ります。

```bash
echo 'ずんだ餅が喉に詰まったら、掃除機で吸いましょう。'| ninvoice
```

# OS毎の問題点
pythonはWindowsに音を出すインターフェイスがあります。
だけどLinuxの場合はないです。aplayとかで音を出すくらい？
一応デフォでaplay使ってますけれど、設定はできます。
MacOSに至っては僕は何も分からないのでなんとも…

# おしまい
VOICEVOXが面白いので遊んでみました。
これから色々と遊んでみようと思いました。