VTube StudioとPython連携してモーション発動させてみた

チュートリアル

バーチャルYouTuber(VTuber)の世界では、配信や動画の中でキャラクターを動かすために様々な技術が用いられています。その中でも、VTube Studioはリアルタイムでのアバター制御に特化したアプリケーションとして広く利用されています。今回は、VTube Studioの機能を拡張し、Pythonを使用して動的にモーションや表情を変化させる方法について解説します。具体的には、APIを通じてVTube Studioに指令を送り、アバターを動かす手順を紹介します。


こちらの記事もおすすめ

VMagicMirrorに命を吹き込む Pythonと Style-Bert-VITS2で リアルタイム感情表現
このチュートリアルでは、Pythonを使用してVMagicMirrorにモーションを付与する方法を説明します。VMagicMirrorは、キーボード入力とモーションを連動させることができるソフトウェアです。このプロセスでは、感情分析を行い、...
AIキャラクター実況をStyle-Bert-VITS2とVMagicMirrorで始めよう
AI技術の進歩により、誰でも簡単に自分のバーチャルキャラクターを作成し、リアルタイムで実況や配信を行うことが可能になりました。この記事では、AI音声合成技術のStyle-Bert-VITS2と、バーチャルキャラクター操作ツールのVMagic...

VTube Studio APIとは?

VTube Studioは、アバターのモーション制御や表情変更、アイテムの操作など、リアルタイム配信で必要とされる機能を提供するアプリケーションです。VTube Studio APIを使用することで、これらの機能を自作のプラグインやスクリプトから制御することが可能になります。例えば、視聴者のコメントや特定のイベントに応じてアバターの表情を変えるといったことが実現できます。

デモ動画

最終的にはこうなります。

API状態の確認

この章では、VTube StudioのAPIとの初期接続と、APIの現在の状態を確認する方法について解説します。このプロセスは、APIとの通信が正常に機能しているかをテストする基本的な手順です。


import asyncio  # 非同期IOを扱うためのモジュールをインポート
import json  # JSONデータのエンコード・デコードを行うモジュールをインポート
import websockets  # WebSocketを扱うためのライブラリをインポート

# 非同期関数send_requestの定義
async def send_request():
    uri = "ws://localhost:8001"  # VTube StudioがリッスンしているWebSocketのURI

    # WebSocket接続を開始
    async with websockets.connect(uri) as websocket:
        request = {
            "apiName": "VTubeStudioPublicAPI",  # APIの名前
            "apiVersion": "1.0",  # 使用しているAPIのバージョン
            "requestID": "MyIDWithLessThan64Characters",  # このリクエストに対するユニークなID
            "messageType": "APIStateRequest"  # リクエストの種類。ここではAPIの状態を問い合わせる
        }

        await websocket.send(json.dumps(request))  # リクエストをJSON形式でエンコードして送信
        response = await websocket.recv()  # レスポンスを待ち受ける
        print(f"Received: {response}")  # レスポンスをコンソールに出力

# 非同期イベントループを取得し、send_request関数を実行
asyncio.get_event_loop().run_until_complete(send_request())

認証プロセス

ここでは、VTube Studio APIを使用するための認証プロセスに焦点を当てます。具体的には、認証トークンの取得と、そのトークンを使用した認証の実施方法について詳しく説明します。

認証トークンのリクエスト

最初のステップは、認証トークンをリクエストすることです。このトークンは、APIに対する後続のリクエストで使用され、プラグインがVTube Studioを操作するための認証を行います。


import asyncio
import json
import websockets

# 認証トークンをリクエストする非同期関数
async def request_token(websocket, plugin_name, plugin_developer, plugin_icon=None):
    request = {
        "apiName": "VTubeStudioPublicAPI",
        "apiVersion": "1.0",
        "requestID": "TokenRequestID",
        "messageType": "AuthenticationTokenRequest",
        "data": {
            "pluginName": plugin_name,  # プラグイン名
            "pluginDeveloper": plugin_developer,  # 開発者名
            "pluginIcon": plugin_icon  # プラグインのアイコン(任意)
        }
    }

    await websocket.send(json.dumps(request))  # リクエストをJSON形式で送信
    response = await websocket.recv()  # レスポンスを待つ
    json_response = json.loads(response)  # JSON形式のレスポンスをパース

    # 認証トークンをレスポンスから取得
    if json_response["messageType"] == "AuthenticationTokenResponse":
        return json_response["data"]["authenticationToken"]
    else:
        return None

認証の実施

トークンを取得したら、そのトークンを使用して認証を行います。このステップでは、APIにプラグインが認証されることを確認します。


# 認証を行う非同期関数
async def authenticate(websocket, plugin_name, plugin_developer, authentication_token):
    request = {
        "apiName": "VTubeStudioPublicAPI",
        "apiVersion": "1.0",
        "requestID": "AuthenticationRequestID",
        "messageType": "AuthenticationRequest",
        "data": {
            "pluginName": plugin_name,  # プラグイン名
            "pluginDeveloper": plugin_developer,  # 開発者名
            "authenticationToken": authentication_token  # 認証トークン
        }
    }

    await websocket.send(json.dumps(request))  # リクエストをJSON形式で送信
    response = await websocket.recv()  # レスポンスを待つ
    json_response = json.loads(response)  # JSON形式のレスポンスをパース

    # 認証の成功または失敗をレスポンスから判断
    if json_response["messageType"] == "AuthenticationResponse":
        return json_response["data"]["authenticated"]
    else:
        return False

メイン関数

最後に、これらの関数を呼び出して実際に認証プロセスを実行します。WebSocketサーバー(VTube Studio)に接続し、認証トークンのリクエストと認証を行います。


# メイン関数
async def main():
    uri = "ws://localhost:8001"  # VTube StudioのWebSocketサーバーURI
    async with websockets.connect(uri) as websocket:
        plugin_name = "My Cool Plugin"  # プラグイン名
        plugin_developer = "My Name"  # 開発者名
        authentication_token = await request_token(websocket, plugin_name, plugin_developer)

        if authentication_token:
            print(f"Token: {authentication_token}")
            is_authenticated = await authenticate(websocket, plugin_name, plugin_developer, authentication_token)
            print(f"Authenticated: {is_authenticated}")
        else:
            print("Token request failed")

# イベントループを起動して、メイン関数を実行
asyncio.get_event_loop().run_until_complete(main())

このプロセスを通じて、プラグインはVTube StudioのAPIを使用するための認証を受け、APIを介してさまざまな操作を行うことが可能になります。

全体コード


import asyncio  # 非同期プログラミングをサポートするためのモジュール
import json  # JSONデータを扱うためのモジュール
import websockets  # WebSocketを扱うためのライブラリ

# 認証トークンをリクエストする非同期関数
async def request_token(websocket, plugin_name, plugin_developer, plugin_icon=None):
    # 認証トークンリクエストのためのリクエストボディを定義
    request = {
        "apiName": "VTubeStudioPublicAPI",  # 使用するAPIの名前
        "apiVersion": "1.0",  # APIのバージョン
        "requestID": "TokenRequestID",  # リクエストの一意識別子
        "messageType": "AuthenticationTokenRequest",  # メッセージタイプ
        "data": {  # リクエストに必要なデータ
            "pluginName": plugin_name,  # プラグイン名
            "pluginDeveloper": plugin_developer,  # 開発者名
            "pluginIcon": plugin_icon  # プラグインのアイコン(オプション)
        }
    }

    # WebSocket経由でリクエストを送信し、レスポンスを待機
    await websocket.send(json.dumps(request))
    response = await websocket.recv()
    json_response = json.loads(response)  # レスポンスをJSON形式で解析

    # 認証トークンが含まれていればそれを返し、そうでなければNoneを返す
    if json_response["messageType"] == "AuthenticationTokenResponse":
        return json_response["data"]["authenticationToken"]
    else:
        return None

# 認証を行う非同期関数
async def authenticate(websocket, plugin_name, plugin_developer, authentication_token):
    # 認証リクエストのためのリクエストボディを定義
    request = {
        "apiName": "VTubeStudioPublicAPI",
        "apiVersion": "1.0",
        "requestID": "AuthenticationRequestID",
        "messageType": "AuthenticationRequest",
        "data": {
            "pluginName": plugin_name,
            "pluginDeveloper": plugin_developer,
            "authenticationToken": authentication_token
        }
    }

    # WebSocket経由でリクエストを送信し、レスポンスを待機
    await websocket.send(json.dumps(request))
    response = await websocket.recv()
    json_response = json.loads(response)  # レスポンスをJSON形式で解析

    # 認証が成功したかどうかを判定
    if json_response["messageType"] == "AuthenticationResponse":
        return json_response["data"]["authenticated"]
    else:
        return False

# メイン関数
async def main():
    uri = "ws://localhost:8001"  # VTube StudioのAPIサーバーのURI
    # WebSocketサーバーに接続
    async with websockets.connect(uri) as websocket:
        plugin_name = "My Cool Plugin"  # プラグイン名
        plugin_developer = "My Name"  # 開発者名
        # 認証トークンをリクエスト
        authentication_token = await request_token(websocket, plugin_name, plugin_developer)

        # 認証トークンが取得できた場合、認証処理を行う
        if authentication_token:
            print(f"Token: {authentication_token}")
            is_authenticated = await authenticate(websocket, plugin_name, plugin_developer, authentication_token)
            print(f"Authenticated: {is_authenticated}")
        else:
            print("Token request failed")  # 認証トークンの取得に失敗した場合

# イベントループを開始し、メイン関数を実行
asyncio.get_event_loop().run_until_complete(main())

ホットキーの取得

この章では、認証後に使用可能な機能の一つであるホットキーの取得方法について説明します。ホットキーを利用することで、VTube Studio内で設定されたアクションをプログラムからトリガーすることが可能になります。

必要なライブラリのインポート

import asyncio  # 非同期処理を行うためのライブラリ
import json  # JSON形式のデータ操作を行うためのライブラリ
import websockets  # WebSocket通信を行うためのライブラリ

import sys  # システムパラメータと関数にアクセスするためのライブラリ
import pprint  # データ構造を読みやすく出力するためのライブラリ

pprint.pprint(sys.path)  # Pythonインタープリタがモジュールを検索するパスを出力

このセクションでは、非同期通信やデータの処理に必要なPythonの標準ライブラリと、WebSocket通信を行うためのwebsocketsライブラリをインポートします。また、システムパスを確認し、データ構造を視覚的に確認するためにpprintを使用します。

システムパスの設定

import os  # OS機能を扱うためのライブラリ

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))  # 親ディレクトリをシステムパスに追加
sys.path.append(os.path.join(os.path.dirname(__file__), '../api'))  # 'api'ディレクトリをシステムパスに追加

このコードは、現在のファイルの位置に基づいて、Pythonがモジュールを探すパスに親ディレクトリとapiディレクトリを追加します。これにより、これらのディレクトリ内のモジュールやパッケージをインポートできるようになります。

認証関連関数のインポート

from api.authentication import request_authentication_token, authenticate_plugin

この行では、先ほどパスに追加したapiディレクトリから、authenticationモジュール内のrequest_authentication_token関数とauthenticate_plugin関数をインポートしています。これらの関数は、VTube Studio APIを使用するための認証プロセスに必要です。

ホットキーの取得

async def get_hotkeys(websocket, model_id=None, live2d_item_filename=None):
    request = {
        "apiName": "VTubeStudioPublicAPI",
        "apiVersion": "1.0",
        "requestID": "UniqueRequestIDLessThan64Characters",
        "messageType": "HotkeysInCurrentModelRequest",
        "data": {}
    }

    if model_id is not None:
        request["data"]["modelID"] = model_id
    if live2d_item_filename is not None:
        request["data"]["live2DItemFileName"] = live2d_item_filename

    await websocket.send(json.dumps(request))
    response = await websocket.recv()
    print(f"Received: {response}")
    pprint.pprint(response)

この関数は、VTube Studioの現在のモデルまたは指定されたモデルのホットキーを取得するために使用されます。model_idlive2d_item_filenameをオプションで指定することで、特定のモデルやLive2Dアイテムのホットキーに絞り込むことが可能です。WebSocketを介してリクエストを送信し、レスポンスを受け取っています。

メイン関数

async def main():
    uri = "ws://localhost:8001"
    async with websockets.connect(uri) as websocket:
        plugin_name = "My Cool Plugin"
        plugin_developer = "My Name"
        authentication_token = await request_authentication_token(websocket, plugin_name, plugin_developer)

        if authentication_token:
            print(f"Token: {authentication_token}")
            is_authenticated = await authenticate_plugin(websocket, plugin_name, plugin_developer, authentication_token)
            print(f"Authenticated: {is_authenticated}")
            if is_authenticated:
                await get_hotkeys(websocket)
        else:
            print("Token request failed")

asyncio.run(main())

最後に、このメイン関数ではVTube StudioのWebSocketサーバーに接続し、認証トークンの取得、認証の実施、そしてホットキーの取得までの一連のプロセスを実行しています。認証が成功した場合にのみホットキーの取得を試み、その結果をコンソールに出力します。

ランダムホットキーのトリガー

ここでは、取得したホットキーの中からランダムに一つ選び、それをトリガーする方法を紹介します。この機能を活用することで、動的かつ予測不可能なアクションを実現できます。

必要なライブラリのインポート

import asyncio  # 非同期処理をサポート
import json  # JSON形式のデータ操作
import websockets  # WebSocket通信

import sys
import pprint  # データの整形出力
import os
import random  # ランダムな選択
import time  # 時間操作

このセクションでは、非同期処理、JSONデータの扱い、WebSocket通信を可能にするライブラリと、システム操作、データの整形出力、ランダムな選択、時間操作を行うためのモジュールをインポートしています。

システムパスの設定

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
sys.path.append(os.path.join(os.path.dirname(__file__), '../api'))

このコードは、スクリプトが存在するディレクトリの親フォルダおよびapiサブフォルダをPythonのモジュール検索パスに追加します。これにより、これらのディレクトリにあるモジュールやパッケージをインポートできるようになります。

認証関連関数のインポート

from api.authentication import request_authentication_token, authenticate_plugin

ここで、認証プロセスに必要な関数request_authentication_tokenauthenticate_pluginapi/authenticationからインポートしています。

ホットキーの取得関数

async def get_hotkeys(websocket):
    request = {
        "apiName": "VTubeStudioPublicAPI",
        "apiVersion": "1.0",
        "requestID": "UniqueRequestIDForHotkeys",
        "messageType": "HotkeysInCurrentModelRequest",
        "data": {}
    }
    await websocket.send(json.dumps(request))
    response = await websocket.recv()
    response_json = json.loads(response)
    if "data" in response_json and "availableHotkeys" in response_json["data"]:
        return response_json["data"]["availableHotkeys"]
    return []

この非同期関数は、WebSocket経由でVTube Studio APIにホットキーのリストを要求し、応答から利用可能なホットキーのリストを取得します。

ランダムにホットキーをトリガーする関数

async def trigger_random_hotkey(websocket, hotkeys):
    if hotkeys:
        hotkey = random.choice(hotkeys)
        hotkey_id = hotkey.get("hotkeyID")
        if hotkey_id:
            request = {
                "apiName": "VTubeStudioPublicAPI",
                "apiVersion": "1.0",
                "requestID": "UniqueRequestIDForTriggering",
                "messageType": "HotkeyTriggerRequest",
                "data": {
                    "hotkeyID": hotkey_id
                }
            }
            await websocket.send(json.dumps(request))
            response = await websocket.recv()
            print(f"Triggered Hotkey Response: {response}")

この関数は、取得したホットキーのリストからランダムに1つ選択し、そのホットキーをトリガーするリクエストをVTube Studio APIに送信します。

メイン関数

async def main():
    uri = "ws://localhost:8001"
    async with websockets.connect(uri) as websocket:
        plugin_name = "My Cool Plugin"
        plugin_developer = "My Name"
        authentication_token = await request_authentication_token(websocket, plugin_name, plugin_developer)
        if authentication_token:
            print(f"Token: {authentication_token}")
            is_authenticated = await authenticate_plugin(websocket, plugin_name, plugin_developer, authentication_token)
            if is_authenticated:
                hotkeys = await get_hotkeys(websocket)
                print(">>>> hotkeys >>>>")
                pprint.pprint(hotkeys)
                await trigger_random_hotkey(websocket, hotkeys)
                time.sleep(5)
                await trigger_random_hotkey(websocket, hotkeys)
        else:
            print("Token request failed")
asyncio.run(main())

メイン関数では、まずWebSocket経由でVTube StudioのAPIサーバーに接続し、プラグインの認証トークンを取得します。認証に成功したら、ホットキーのリストを取得し、その中からランダムにホットキーを2回トリガーします。この過程を通じて、VTube Studioでのプラグイン開発の基本的な流れを理解することができます。

オーディオ連動ホットキーのトリガー

最後の章では、オーディオファイルの再生をトリガーとして、ホットキーを発動させる高度な利用方法について解説します。この方法を使うことで、音声や音楽に合わせてキャラクターが動くような演出が可能になります。

必要なライブラリのインポート

import asyncio  # 非同期処理をサポート
import json  # JSON形式のデータ操作
import os  # OSの機能、特にファイルパス操作
import re  # 正規表現
import random  # ランダムな選択
import websockets  # WebSocket通信
from pygame import mixer  # オーディオ再生
import sys

このセクションでは、非同期処理、JSONデータの扱い、ファイルパス操作、正規表現、ランダムな選択、WebSocket通信、オーディオ再生を可能にするライブラリやモジュールをインポートしています。

システムパスの設定

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
sys.path.append(os.path.join(os.path.dirname(__file__), '../api'))

このコードは、スクリプトが存在するディレクトリの親フォルダおよびapiサブフォルダをPythonのモジュール検索パスに追加します。これにより、これらのディレクトリにあるモジュールやパッケージをインポートできるようになります。

認証関連関数のインポート

from api.authentication import request_authentication_token, authenticate_plugin

ここで、認証プロセスに必要な関数request_authentication_tokenauthenticate_pluginapi/authenticationからインポートしています。

ホットキーの取得とトリガー関数

async def get_hotkeys(websocket):
    # 省略

async def trigger_random_hotkey(websocket, hotkeys):
    # 省略

これらの関数は、前述のコードブロックで解説したものと同じです。get_hotkeys関数はVTube Studio APIを通じて利用可能なホットキーのリストを取得し、trigger_random_hotkey関数は取得したホットキーのリストからランダムに1つ選択してトリガーします。

オーディオファイルの再生とホットキーのトリガー

async def play_audio_and_trigger_hotkeys(websocket, folder_path='audio/Word2Motion'):
    files = [file for file in os.listdir(folder_path) if file.endswith('.wav')]
    sorted_files = sorted(files, key=lambda file: int(re.search(r'\d+', file).group()) if re.search(r'\d+', file) else 0)

    mixer.init()
    for file in sorted_files:
        file_path = os.path.join(folder_path, file)
        print(f"再生中: {file}")
        mixer.music.load(file_path)
        mixer.music.play()
        while mixer.music.get_busy():
            await asyncio.sleep(1)
        await trigger_random_hotkey(websocket, await get_hotkeys(websocket))

この関数は、指定されたフォルダ内の.wavオーディオファイルを検索し、ファイル名に含まれる数字に基づいてソートした後、順番に再生します。各ファイルの再生が終了すると、ランダムに選択したホットキーがトリガーされます。これにより、オーディオ再生に連動してVTube Studio内で特定のアクションが実行されるようになります。

メイン関数

async def main():
    uri = "ws://localhost:8001"
    async with websockets.connect(uri) as websocket:
        plugin_name = "My Cool Plugin"
        plugin_developer = "My Name"
        authentication_token = await request_authentication_token(websocket, plugin_name, plugin_developer)
        if authentication_token:
            print(f"Token: {authentication_token}")
            is_authenticated = await authenticate_plugin(websocket, plugin_name, plugin_developer, authentication_token)
            if is_authenticated:
                await play_audio_and_trigger_hotkeys(websocket)

asyncio.run(main())

メイン関数では、WebSocket経由でVTube StudioのAPIサーバーに接続し、プラグインの認証を行った後、play_audio_and_trigger_hotkeys関数を呼び出してオーディオファイルの再生とホットキーのトリガーを開始します。これにより、VTube Studioでのインタラクティブなエンターテインメントを実現するための基礎を提供します。

まとめ

VTube StudioとPythonを連携させることで、VTuberの配信をよりダイナミックに、そしてインタラクティブにすることができます。この記事が、VTube Studio APIの基礎から認証、さらにはモーション制御の方法まで、一連の流れを理解する手助けになれば幸いです。

リポジトリ

GitHub - Sunwood-ai-labs/VTubeStudio: VTube Studio API Development Page
VTube Studio API Development Page. Contribute to Sunwood-ai-labs/VTubeStudio development by creating an account on GitHub.

活用したGPTs

ChatGPT - VTubeStudio API ドキュメント
VTubeStudio API ドキュメント

コメント

タイトルとURLをコピーしました