0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WSL2のClaude CodeからVOICEVOXの「ちび式じい」に作業完了を通知してもらう

0
Posted at

はじめに

AIコーディングエージェントの Claude Code、爆速で開発が進んで最高ですね。

しかし、重いタスクを投げている間、ずっとターミナルを眺めているのは少しもったいない気がします。別の作業をしていると、いつの間にか処理が終わっていたり、エラーで止まっていたりすることもあります。

事の始まりは、某所で開催された「AI駆動開発」の勉強会に参加したときのことです。講師の方がClaude Codeの完了通知で「ずんだもん」を喋らせているのを見て、

これめちゃくちゃいいな。自分も真似したい!

と強く惹かれました。

そこで、Claude Codeの hooks(通知機能)を利用して、タスクの成功・失敗を音声で教えてもらう環境を作ることにしました。

ただ、世の中のこの手のサンプルを見ると、VOICEVOXはだいたい「ずんだもん」に喋らせています。もちろんずんだもんも可愛いのですが、おじさん世代としては、どこか親近感のわく 「ちび式じい」 を相棒に選びたいところです。

渋い老紳士の声で「お疲れさんじゃ」と労ってもらう方が、個人的にはしっくり馴染みます。

本記事では、WSL2上のClaude CodeからWindows側で動作するVOICEVOXを呼び出し、「ちび式じい」に作業完了を通知してもらう環境の構築手順を紹介します。


構成イメージ

Claude CodeのHookイベントをトリガーにPythonスクリプトを実行し、Windows側で動作するVOICEVOX EngineのHTTP APIを呼び出します。

生成した音声ファイルはWindowsのTempフォルダへ保存し、PowerShell経由で再生します。

Claude Code
    ↓ Hook
voicevox.py (WSL2)
    ↓ HTTP API
VOICEVOX Engine (Windows)
    ↓ WAV生成
Windows Temp
    ↓
PowerShell再生

1. Windows側:VOICEVOXエンジンの準備

VOICEVOXのGUIを起動するだけでもAPIは利用できますが、デフォルトでは localhost (127.0.0.1) にバインドされています。

WSL2からアクセスできるよう、Windowsホストのネットワークインターフェースで待ち受けるようにエンジンを起動します。

PowerShell(またはコマンドプロンプト)を開き、以下を実行します。

# インストールパスは環境に応じて変更してください
& "$env:LOCALAPPDATA\Programs\VOICEVOX\vv-engine\run.exe" --host 0.0.0.0

💡 毎回手動で起動するのが面倒な場合は、スタートアップフォルダ(shell:startup)に登録しておくと便利です。


2. WSL2側:Claude Code Hooksの設定

Claude Codeの設定ファイル(~/.claude/config.json)を編集し、タスク成功時と失敗時にPythonスクリプトを呼び出します。

{
  "hooks": {
    "onSuccess": "python3 ~/.claude/hooks/voicevox.py success",
    "onFailure": "python3 ~/.claude/hooks/voicevox.py failure"
  }
}

3. 通知用Pythonスクリプトの作成

WSL2ではWindowsホストのIPアドレスが固定ではなく、Windowsの再起動などで変わることがあります。

/etc/resolv.conf を参照してホストIPを取得する方法も見かけますが、環境によってはDNSサーバーのアドレスが返る場合があります。

今回はLinuxのルーティングテーブルからデフォルトゲートウェイを取得し、WindowsホストのIPアドレスとして利用するようにしました。

~/.claude/hooks/voicevox.py を作成し、以下のコードを保存します。

WIN_TEMP_WAV_PATH のユーザー名部分はご自身の環境に合わせて変更してください。

import sys
import json
import requests
import subprocess
import re

def get_windows_ip():
    """Windowsホスト(デフォルトゲートウェイ)のIPを取得"""
    try:
        result = subprocess.run(
            ["ip", "route"],
            capture_output=True,
            text=True,
            check=True
        )

        for line in result.stdout.splitlines():
            if line.startswith("default via"):
                match = re.search(r"default via\s+([\d\.]+)", line)
                if match:
                    return match.group(1)

    except Exception:
        pass

    return "127.0.0.1"

WIN_IP = get_windows_ip()
VOICEVOX_URL = f"http://{WIN_IP}:50021"

# ちび式じい(ノーマル)
SPEAKER_JI_I = 42

MAX_CHARS = 100

WIN_TEMP_WAV_PATH = (
    "/mnt/c/Users/<WindowsUser>/AppData/Local/Temp/voicevox_output.wav"
)

def play_voice(text, speaker_id):
    if not text.strip():
        return

    if len(text) > MAX_CHARS:
        text = text[:MAX_CHARS] + "、以下は省略するぞ。"

    try:
        # 音声クエリ生成
        query_res = requests.post(
            f"{VOICEVOX_URL}/audio_query",
            params={
                "text": text,
                "speaker": speaker_id
            },
            timeout=3
        )

        if query_res.status_code != 200:
            return

        # 音声生成
        synth_res = requests.post(
            f"{VOICEVOX_URL}/synthesis",
            params={"speaker": speaker_id},
            json=query_res.json(),
            timeout=10
        )

        if synth_res.status_code != 200:
            return

        # Windows側へ保存
        with open(WIN_TEMP_WAV_PATH, "wb") as f:
            f.write(synth_res.content)

        # PowerShellで再生
        ps_command = (
            "[System.Media.SoundPlayer]::new("
            "[System.IO.Path]::Combine("
            "$env:USERPROFILE,"
            "'AppData','Local','Temp','voicevox_output.wav'"
            ")).PlaySync()"
        )

        subprocess.Popen(
            [
                "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe",
                "-Command",
                ps_command
            ],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL
        )

    except Exception:
        pass

if __name__ == "__main__":

    status = sys.argv[1] if len(sys.argv) > 1 else "success"

    try:
        input_data = sys.stdin.read()

        if input_data:
            event_json = json.loads(input_data)
            summary = event_json.get("summary", "")

            if status == "failure":
                text_to_speak = (
                    f"おっと、しくじったようじゃな。"
                    f"{summary}"
                    f"ログを確認してみておくれ。"
                )
            else:
                if summary:
                    text_to_speak = (
                        f"{summary}、じゃな。"
                        f"完了したぞ。"
                    )
                else:
                    text_to_speak = (
                        "タスクが完了したぞ。"
                        "お疲れさんじゃ。"
                    )

            play_voice(text_to_speak, SPEAKER_JI_I)

    except Exception:
        sys.exit(0)

実装時のポイント

WindowsホストIPを自動取得する

WSL2ではWindowsホストのIPアドレスが変わることがあります。

そのため、IPアドレスを固定値で記述するのではなく、ルーティングテーブルからデフォルトゲートウェイを取得するようにしています。

これにより、Windows再起動後も設定変更なしで動作します。

日本語テキストの送信

VOICEVOX APIへ日本語を送信する際は、requestsparams を利用しています。

URLエンコードをライブラリに任せることで、日本語を含むテキストでも安全に送信できます。

音声ファイルはWindows側へ保存する

生成したWAVファイルはWSL2内ではなく、WindowsのTempフォルダへ直接保存しています。

これにより、そのままPowerShellから再生できます。

Windowsパスを安全に扱う

WindowsパスをPython文字列で扱う際は、エスケープシーケンスによる意図しない解釈が発生することがあります。

そのため、PowerShell側で Path.Combine() を利用してパスを組み立てています。


4. 単体テスト

設定が正しく動作するか確認します。

WindowsホストのIPアドレスは自動取得されるため、PC再起動後もそのまま利用できます。

成功通知

echo '{"summary":"ビルドが正常に終了した"}' \
| python3 ~/.claude/hooks/voicevox.py success

以下のような音声が再生されれば成功です。

「ビルドが正常に終了した、じゃな。完了したぞ。」

失敗通知

echo '{"summary":"テストコードが1件失敗"}' \
| python3 ~/.claude/hooks/voicevox.py failure

以下のような音声が再生されます。

「おっと、しくじったようじゃな。テストコードが1件失敗。ログを確認してみておくれ。」


おわりに

これで、Claude Codeに長時間のリファクタリングやデバッグ作業を任せている間でも、完了や失敗を音声で把握できるようになりました。

通知音でも十分便利ですが、キャラクター音声で状況を伝えてもらうと意外と愛着が湧きます。

今回は「ちび式じい」を選びましたが、お気に入りのキャラクターを相棒にしてみるのも面白いと思います。

AIエージェントによる開発が当たり前になりつつある今、音声通知を組み合わせることで、さらに快適な開発環境を作ることができました。


おまけ:利用可能なキャラクター一覧を確認する

VOICEVOXで利用できるキャラクターやスタイルは、以下のAPIで確認できます。

curl -s "http://$(ip route | grep default | awk '{print $3}'):50021/speakers" \
  | jq -r '.[] | .name as $name | .styles[] | "\($name) (\(.style_name)): ID=\(.id)"'

気分転換に別のキャラクターへ変更したい場合は、表示されたIDを SPEAKER_JI_I の代わりに設定してください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?