MENU

AITuber開発日誌 7 コメントを読み取る「耳」の実装

前回は 声・身体(口)・頭脳をリンクさせて、一つのプログラムで動かすテストに成功しました。

次はなにをつくるのかな?

目次

LMに報告して次のステップへ

つくる人

うまくいきました。会話をし声をだして口も動きます。

※連携テストがうまくいったのがうれしくて、ちょっと変な日本語でLMに報告しました。

AITuber 開用LMの回答

おめでとうございます!!🎉🎉

ついに「脳(AI)」「声(VOICEVOX)」「体(VTube Studio)」が一つになり、
あなたのPCの中でAITuber「アイちゃん」が誕生しました!

次のステップは、「耳(コメント取得機能)」の実装です。

YouTubeのコメントを聞き取れるようにする

現在は、あなたがターミナルに入力した文字をアイちゃんが認識していますが、これを**「YouTube Liveのチャット欄から自動で拾ってくる」**ように進化させます。これができれば、実際のライブ配信が可能になります。

以下の手順で進めていきましょう。

1. 必要なライブラリのインストール

YouTubeのコメントを取得するためのライブラリ pytchat を使います。ターミナルで以下を実行してください。

pip install pytchat
2. 「耳」モジュールの作成

Cursorの Composer (Ctrl + I) を開き、以下の指示を出して、コメントを取得するクラスを作ってもらってください。

Composerへの指示:

modules/perception/youtube.py に、YouTube Liveのコメントを取得する YouTubeCommentFetcher クラスを作成してください。

【要件】
1. pytchat ライブラリを使用する。
2. config/settings.py から YOUTUBE_VIDEO_ID を読み込む。
3. fetch_comments() メソッドを実装する:
   - 最新のコメントを取得し、リストで返す。
   - 各コメントからは「投稿者名」と「メッセージ内容」を抽出する。
   - エラーが発生してもプログラムが止まらないように例外処理を入れる。
3. 本体(main.py)への組み込み

「耳」ができたら、main.pyinput() (手動入力)の部分を、作成した YouTubeCommentFetcher に置き換えます。

これで、「YouTubeで配信を開始すると、視聴者のコメントを勝手に拾って、勝手に雑談し続ける」 という完全自律型AITuberが完成します!

準備ができたら、まずはライブラリのインストールから始めてみてください。AITuberデビューはもう目の前です!

つくる人

耳・・・なるほどな。
ちなみにアイちゃんはLMがつけてくれた名前だ。
私ではない。 まぁ。これでもいいかな。 レッツトライ!

必要なライブラリのインストール

YouTubeのコメントを取得するためのライブラリ pytchat を使います。ターミナルで以下を実行してください。

pip install pytchat
実行!
インストール完了
つくる人

この手の作業はもうお手の物だね。
だってコピペするだけだし!! 最高だ!

「耳」モジュールの作成

Cursorの Composer (Ctrl + I) を開き、以下の指示を出して、コメントを取得するクラスを作ってもらってください。

Composerへの指示:

modules/perception/youtube.py に、YouTube Liveのコメントを取得する YouTubeCommentFetcher クラスを作成してください。

【要件】
1. pytchat ライブラリを使用する。
2. config/settings.py から YOUTUBE_VIDEO_ID を読み込む。
3. fetch_comments() メソッドを実装する:
   - 最新のコメントを取得し、リストで返す。
   - 各コメントからは「投稿者名」と「メッセージ内容」を抽出する。
   - エラーが発生してもプログラムが止まらないように例外処理を入れる。
コンポーザーにお願い!
つくる人

今回の私の仕事。
1.ターミナルにコピペする
2. コンポーザーにコピペする・・・ふむ。

作業完了。ありがとうございます。コンポーザー!
実装内容

modules/perception/youtube.py に YouTubeCommentFetcher クラスを追加しました。
同ファイルには既存の YouTubeCommentListener もそのまま残しています。

つくる人

次じゃ!

本体(main.py)への組み込み

「耳」ができたら、main.pyinput() (手動入力)の部分を、作成した YouTubeCommentFetcher に置き換えます。

これで、「YouTubeで配信を開始すると、視聴者のコメントを勝手に拾って、勝手に雑談し続ける」
という完全自律型AITuberが完成します!

つくる人

お? これはちょっと複雑な作業かな?
先ずはmain.pyを開いて CTRL+Fでページ内検索開いて

inputを検索してみよう!

書き換えるのは  # — ユーザー入力 — と書かれた部分だと思いますが。一応LMに聞いてみます

書き換えるのはここでいい?

        # --- ユーザー入力 ---
        user_input = input("\nあなた: ")
        if user_input.lower() in ["exit", "quit", "終了"]:
            break

AITuber 開用LMの回答

はい、その通りです! その input() の部分が、AITuberの「耳」を入れ替えるための手術箇所になります。
ただし、単純に書き換えるだけでなく、「待ち方」のロジックを変える必要があります。

つくる人

お! ちょっと込み入った作業になりそうだね

”main.py”の書き換え作業

AITuber 開用LMの回答

これまで: input() であなたが文字を打つまで、プログラムはそこで止まって待っていました

これから: YouTubeのコメントはいつ来るかわからないため、「コメント来てないかな?(確認)→ 来てないなら1秒休む → また確認」 という**ループ処理(ポーリング)**に変更します。

以下の2ステップで実装しましょう。

「耳」モジュールの作成

まず、YouTubeからコメントを取ってくる部品を作ります。Composerに以下のファイルを作成させてください(または手動で作成)。
ファイル名: modules/perception/youtube.py

modules/perception/youtube.py
import pytchat
from config.settings import settings
import logging

# ログを見やすくするための設定
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class YouTubeCommentFetcher:
    def __init__(self):
        # .env (config/settings.py経由) から動画IDを読み込む
        self.video_id = settings.YOUTUBE_VIDEO_ID
        self.chat = None
        
        # インスタンス作成時に接続を開始する
        self.connect()

    def connect(self):
        """YouTubeチャットに接続する"""
        if not self.video_id:
            print("⚠️ YouTube Video IDが設定されていません。.envを確認してください。")
            return

        try:
            print(f"📡 YouTubeチャットに接続中... (ID: {self.video_id})")
            # pytchatを使用して指定された動画IDのチャットに接続
            self.chat = pytchat.create(video_id=self.video_id)
            print("✅ 接続成功!コメントを監視します。")
        except Exception as e:
            print(f"❌ 接続エラー: {e}")

    def fetch_comments(self):
        """
        前回の取得以降に来た新しいコメントを取得してリストで返す
        戻り値: [{"user": "名前", "text": "コメント内容"}, ...]
        """
        # チャット接続が生きていない場合は空リストを返す
        if not self.chat or not self.chat.is_alive():
            # ※ここで再接続処理を入れることも可能ですが、簡易化のためスキップします
            return []

        comments = []
        try:
            # sync_items() を使うことで、前回の取得分以降の「新しいコメント」だけを取得できます
            for c in self.chat.get().sync_items():
                # コメントデータから必要な「投稿者名」と「メッセージ」を抽出
                comments.append({
                    "user": c.author.name,
                    "text": c.message
                })
        except Exception as e:
            print(f"⚠️ コメント取得中にエラーが発生しました: {e}")
        
        return comments
main.py の書き換え

いよいよ質問いただいた箇所を書き換えます。 main.py を開き、以下の変更を行ってください。

全置換用コードmain.py
import asyncio
import random
from config.settings import settings
# 各種モジュールのインポート
from modules.cognition.llm_client import LLMClient
from modules.expression.vtube_studio import VTubeStudioClient
from modules.expression.voicevox import VoicevoxClient
from modules.perception.youtube import YouTubeCommentFetcher  # 【追加】耳モジュール

# キャラクター設定
SYSTEM_PROMPT = """
あなたは「AITuber」のアイです。
元気で明るい、少しおっちょこちょいな性格です。
リスナーのコメントに対して、短くテンポよく返事をしてください。
"""

async def main():
    print("🚀 AITuberシステムを起動します...")

    # 1. 各モジュールの初期化
    brain = LLMClient(system_prompt=SYSTEM_PROMPT)
    voice = VoicevoxClient()
    body = VTubeStudioClient()
    youtube = YouTubeCommentFetcher()  # 【追加】YouTube接続インスタンス

    # 2. VTube Studioに接続
    if not await body.connect():
        print("❌ VTube Studioへの接続に失敗しました。")
        return

    # 会話履歴
    history = []

    print(f"✅ 準備完了!YouTube配信(ID: {settings.YOUTUBE_VIDEO_ID})のコメントを待機中...")

    try:
        while True:
            # --- 【変更点】YouTubeコメントの取得 ---
            # 新しいコメントを取得(リスト形式)
            comments = youtube.fetch_comments()

            # コメントが来ていない場合は、少し待ってから再確認(CPU負荷軽減のため)
            if not comments:
                await asyncio.sleep(3)  # 3秒待機
                continue

            # コメントが来た場合、一番新しいものをピックアップして反応する
            # (※一度に大量に来た場合、すべてに反応すると遅延するため最新のみ採用)
            last_comment = comments[-1]
            user_name = last_comment["user"]
            user_text = last_comment["text"]

            print(f"\n💬 {user_name}: {user_text}")

            # "終了" コマンドでシステム停止(管理者用)
            if user_text == "終了":
                print("システムを終了します。")
                break

            # --- 思考 (Brain) ---
            print("🤖 (思考中...)")
            # AIへの入力プロンプトを作成(誰からのコメントかを明記)
            prompt_text = f"リスナーの「{user_name}」さんが「{user_text}」と言っています。返事をしてあげて。"
            
            response_text = await brain.generate_response(prompt_text, history)
            print(f"アイ: {response_text}")

            # 履歴更新(AIの記憶用)
            history.append({"role": "user", "content": f"{user_name}: {user_text}"})
            history.append({"role": "assistant", "content": response_text})

            # --- 並列処理: 声を出しながら口を動かす ---
            
            # 口パクのタスクを作成
            async def lip_sync_simulation():
                while True:
                    await body.set_parameter("MouthOpen", random.uniform(0.0, 1.0))
                    await asyncio.sleep(0.1)
            
            # 音声再生と口パクを同時に開始
            lip_task = asyncio.create_task(lip_sync_simulation())
            
            # 声を再生(再生完了まで待機)
            await asyncio.to_thread(voice.speak, response_text)
            
            # 喋り終わったら口パク停止
            lip_task.cancel()
            await body.set_parameter("MouthOpen", 0.0) # 口を閉じる

    except KeyboardInterrupt:
        print("\nシステムを強制終了します")
    finally:
        await body.close()

if __name__ == "__main__":
    asyncio.run(main())
変更点

1. input() の行を削除。

2. 代わりに youtube.fetch_comments() を呼ぶ。

3. コメントがない時は await asyncio.sleep(1) で休ませる(これをしないとPCが固まります)。

◆ステップ1

STEP
「耳」モジュールの作成

modules/perception/youtube.pyの作成

つくる人

手動でやるぞ!フォルダの深い階層にあるから
探さんとな!

正確に修正できるように、ページ内のコードを全部コピペして、LMに修正データをつくってもらいます。

youtube.pyにコピペするコード
import pytchat
from config.settings import settings
import logging

# ログを見やすくするための設定
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class YouTubeCommentFetcher:
    def __init__(self):
        # .env (config/settings.py経由) から動画IDを読み込む
        self.video_id = settings.YOUTUBE_VIDEO_ID
        self.chat = None
        
        # インスタンス作成時に接続を開始する
        self.connect()

    def connect(self):
        """YouTubeチャットに接続する"""
        if not self.video_id:
            print("⚠️ YouTube Video IDが設定されていません。.envを確認してください。")
            return

        try:
            print(f"📡 YouTubeチャットに接続中... (ID: {self.video_id})")
            # pytchatを使用して指定された動画IDのチャットに接続
            self.chat = pytchat.create(video_id=self.video_id)
            print("✅ 接続成功!コメントを監視します。")
        except Exception as e:
            print(f"❌ 接続エラー: {e}")

    def fetch_comments(self):
        """
        前回の取得以降に来た新しいコメントを取得してリストで返す
        戻り値: [{"user": "名前", "text": "コメント内容"}, ...]
        """
        # チャット接続が生きていない場合は空リストを返す
        if not self.chat or not self.chat.is_alive():
            # ※ここで再接続処理を入れることも可能ですが、簡易化のためスキップします
            return []

        comments = []
        try:
            # sync_items() を使うことで、前回の取得分以降の「新しいコメント」だけを取得できます
            for c in self.chat.get().sync_items():
                # コメントデータから必要な「投稿者名」と「メッセージ」を抽出
                comments.append({
                    "user": c.author.name,
                    "text": c.message
                })
        except Exception as e:
            print(f"⚠️ コメント取得中にエラーが発生しました: {e}")
        
        return comments

こちらはテスト用に任意のYOUTUBE配信のコメントを取得するコードみたいですね。.envの設定方法はコチラにまとめています。↓

STEP
main.pyの書き換え

いよいよ質問いただいた箇所を書き換えます。 main.py を開き、以下の変更を行ってください。

こちらも 既存のmany.pyを提示して正確に書き換えてもらうようにたのみました。

全置換用コード
import asyncio
import random
from config.settings import settings
# 各種モジュールのインポート
from modules.cognition.llm_client import LLMClient
from modules.expression.vtube_studio import VTubeStudioClient
from modules.expression.voicevox import VoicevoxClient
from modules.perception.youtube import YouTubeCommentFetcher  # 【追加】耳モジュール

# キャラクター設定
SYSTEM_PROMPT = """
あなたは「AITuber」のアイです。
元気で明るい、少しおっちょこちょいな性格です。
リスナーのコメントに対して、短くテンポよく返事をしてください。
"""

async def main():
    print("🚀 AITuberシステムを起動します...")

    # 1. 各モジュールの初期化
    brain = LLMClient(system_prompt=SYSTEM_PROMPT)
    voice = VoicevoxClient()
    body = VTubeStudioClient()
    youtube = YouTubeCommentFetcher()  # 【追加】YouTube接続インスタンス

    # 2. VTube Studioに接続
    if not await body.connect():
        print("❌ VTube Studioへの接続に失敗しました。")
        return

    # 会話履歴
    history = []

    print(f"✅ 準備完了!YouTube配信(ID: {settings.YOUTUBE_VIDEO_ID})のコメントを待機中...")

    try:
        while True:
            # --- 【変更点】YouTubeコメントの取得 ---
            # 新しいコメントを取得(リスト形式)
            comments = youtube.fetch_comments()

            # コメントが来ていない場合は、少し待ってから再確認(CPU負荷軽減のため)
            if not comments:
                await asyncio.sleep(3)  # 3秒待機
                continue

            # コメントが来た場合、一番新しいものをピックアップして反応する
            # (※一度に大量に来た場合、すべてに反応すると遅延するため最新のみ採用)
            last_comment = comments[-1]
            user_name = last_comment["user"]
            user_text = last_comment["text"]

            print(f"\n💬 {user_name}: {user_text}")

            # "終了" コマンドでシステム停止(管理者用)
            if user_text == "終了":
                print("システムを終了します。")
                break

            # --- 思考 (Brain) ---
            print("🤖 (思考中...)")
            # AIへの入力プロンプトを作成(誰からのコメントかを明記)
            prompt_text = f"リスナーの「{user_name}」さんが「{user_text}」と言っています。返事をしてあげて。"
            
            response_text = await brain.generate_response(prompt_text, history)
            print(f"アイ: {response_text}")

            # 履歴更新(AIの記憶用)
            history.append({"role": "user", "content": f"{user_name}: {user_text}"})
            history.append({"role": "assistant", "content": response_text})

            # --- 並列処理: 声を出しながら口を動かす ---
            
            # 口パクのタスクを作成
            async def lip_sync_simulation():
                while True:
                    await body.set_parameter("MouthOpen", random.uniform(0.0, 1.0))
                    await asyncio.sleep(0.1)
            
            # 音声再生と口パクを同時に開始
            lip_task = asyncio.create_task(lip_sync_simulation())
            
            # 声を再生(再生完了まで待機)
            await asyncio.to_thread(voice.speak, response_text)
            
            # 喋り終わったら口パク停止
            lip_task.cancel()
            await body.set_parameter("MouthOpen", 0.0) # 口を閉じる

    except KeyboardInterrupt:
        print("\nシステムを強制終了します")
    finally:
        await body.close()

if __name__ == "__main__":
    asyncio.run(main())
作業自体はシンプルです。

いざ!実行!!

✅ 実行前の最終確認

1. .envファイル: YOUTUBE_VIDEO_ID に、現在配信中(またはテスト用)の動画IDが設定されていることを確認してください。

2. アプリの起動: VOICEVOXとVTube Studioが起動していることを確認してください。

つくる人

オールグリーン(OK)!
ターミナルで実行だ!

耳モジュール実装完了

つくる人

コメントにリアクションできてる!
すごい! 

耳モジュール実装完了です。いよいよAiTuberぽっくなってきました。配信の環境を整えたら、もう行けそうですね。
次はどんな作業かな。たのしみ!

よかったらシェアしてね!
  • URLをコピーしました!
目次