【Python×Gemma3】Windowsで美少女AIに「無限カードバトル」を実況させたらエラー地獄だった件【Kokoro TTS完全攻略】

本ページはプロモーションが含まれています
国内のAI狂い

やっほー!国内のAI狂いだよ!✨

今回はマジで神回!Windowsで「美少女AIによる無限カードバトル実況」を構築したら、地獄のようなエラー沼にハマったけど、執念で解決したから全部共有するよ!🔥

目次

なぜ人類は「無限デュエル」を求めるのか?

ねえみんな、「一生終わらないカードゲームのアニメ」を見続けたいと思ったことない?私はある!!😤
最新の軽量最強モデル「Gemma 3 (4B)」と、話題の超高音質TTS「Kokoro」を組み合わせれば、永遠に厨二病バトルを繰り広げるAIが作れるんじゃないか…?

そう思ったが吉日!早速Pythonで実装を始めたんだけど、そこにはWindows特有の「音声合成ライブラリの罠」が大量に潜んでいたの…。
今回は、私が踏み抜いた地雷と、その解決策、そして完成した「完全動作コード」を包み隠さず公開するよ!

踏み抜いた地雷たち(Windowsの呪い)💣

Linuxなら一瞬で終わる話なんだけど、WindowsでPythonの音声周りを触ると、本当にろくなことがないよね!😭
私が遭遇したエラーの数々を見てくれ…。

1. espeakのエラー地獄

[TTS Error] language "j" is not supported by the espeak backend

Kokoro TTSを使おうとすると、「espeakなんて知らねーよ!」って怒られるやつ。
Windowsにespeakを入れるのはパス設定とか超面倒くさい!

2. mojimojiのビルドエラー

「じゃあespeakを使わずに、Pythonだけで日本語処理できる『misaki』を使おう!」と思ったらこれだよ。

error: Microsoft Visual C++ 14.0 or greater is required.

出たーーー!!Visual C++ Build Tools入れろおじさん!!😡
Pythonで日本語処理を高速化するライブラリ mojimoji は、C++コンパイラがないとインストールすらさせてくれないの…。

3. ライブラリの無視(これが一番キツかった)

苦労して misaki を入れたのに、音声生成ライブラリ(kokoro-onnx)側がそれを認識せず、頑なに espeak を使おうとしてエラーを吐き続ける現象…。
「せっかく入れた日本語エンジンを使ってよ!!😭」 って叫びながらコードを書き直したよ。

解決策:手動G2P変換でライブラリを黙らせる!💪

ライブラリの自動判別に頼るから裏切られるんだね。
だから、「私たちの手で日本語を『音素(発音記号)』に変換してから、音声生成させる」 という力技で解決したよ!

  1. Visual Studio Build Toolsを入れる(これだけは必須!逃げちゃダメ!)
  2. pip install misaki[ja] で日本語エンジンを入れる。
  3. コード内で misaki を使って音素に変換し、それを kokoro に直接渡す!

これでWindowsでも100%、美少女ボイスが再生されるようになったよ!✨

【完全版】無限デュエル実況コード (Python)

これこそが血と汗の結晶!💎
OllamaがインストールされているWindows PCなら、コピペするだけで動くよ!
(事前に ollama pull gemma3:4b しておいてね!)

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

pip install ollama colorama kokoro-onnx sounddevice numpy misaki[ja]

misaki[ja] のインストールでエラーが出る人は、Visual Studio Build Tools の「C++によるデスクトップ開発」を入れてね!

Pythonスクリプト (infinite_duel.py)

import ollama
import time
import sys
import os
import re
import sounddevice as sd
import numpy as np
import urllib.request
from kokoro_onnx import Kokoro
from colorama import init, Fore, Style

# ---------------------------------------------------------
# 1. 日本語化ライブラリ 'misaki' の準備
# ---------------------------------------------------------
try:
    # 'misaki' というのは、日本語を「発音記号」に変換してくれる通訳さんです!
    from misaki import ja
    g2p = ja.JAG2P()
    print(f"{Fore.GREEN}[System] 日本語エンジン(misaki) ロード完了!")
except ImportError as e:
    print(f"{Fore.RED}[Error] 'misaki' の読み込みに失敗したよ!")
    print(f"詳細: {e}")
    print(f"{Fore.RED}対処法: 'pip install misaki[ja]' を実行してね!")
    sys.exit(1)

init(autoreset=True)

# ---------------------------------------------------------
# 設定エリア
# ---------------------------------------------------------
# 使用するモデル名 (Gemma 3 4B 推奨!)
MODEL_NAME = 'gemma3:4b'

# Kokoroモデルファイルの設定 (ネットから自動ダウンロードするためのURL)
KOKORO_MODEL_FILE = "kokoro-v1.0.onnx"
KOKORO_VOICES_FILE = "voices-v1.0.bin"
KOKORO_MODEL_URL = "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx"
KOKORO_VOICES_URL = "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/voices-v1.0.bin"

# ボイス設定:jf_alpha (日本人の女の子ボイス)
DEFAULT_VOICE_NAME = "jf_alpha" 

# 無限に実況を続けさせるための「思考停止禁止」プロンプト
DUEL_SYSTEM = """あなたは熱狂的な決闘者『あいちゃん』です。一人二役でデュエルを無限に実況してください。
【鉄の掟】
1. 絶対に「お後がよろしいようで」「終了」などの締めの言葉を口にしないで。
2. 枠線(━━)やセパレーターは一切使わず、前のターンの続きから即座に次の描写を始めて。
3. 2〜3ターン分の攻防を1つの返答にまとめ、常に「次はどうなるんだにゃ!?」という引きで終わって。
4. ライフ、手札、フィールドの状況を論理的に引き継いでください。"""

class KokoroSpeaker:
    def __init__(self, model_file, voices_file):
        self.enabled = False
        self.voice_name = DEFAULT_VOICE_NAME 
        
        # モデルがPCになければ、ネットから自動でダウンロードしてくるよ!
        self.ensure_file(model_file, KOKORO_MODEL_URL)
        self.ensure_file(voices_file, KOKORO_VOICES_URL)

        if not os.path.exists(model_file) or not os.path.exists(voices_file):
            print(f"{Fore.YELLOW}[Warning] 音声モデルがないからテキストのみでいくね。")
            return
            
        try:
            # 音声生成エンジンの起動!
            self.kokoro = Kokoro(model_file, voices_file)
            self.enabled = True
            
            # 指定したボイスがあるかチェック。なければ似たような声を探す賢い機能つき!
            if hasattr(self.kokoro, 'get_voices'):
                voices = self.kokoro.get_voices()
                if self.voice_name not in voices and voices:
                    print(f"{Fore.YELLOW}[Info] 指定ボイス '{self.voice_name}' が見つからないみたい。")
                    fallback = next((v for v in voices if 'jf' in v), None) # 日本語女性優先
                    if not fallback:
                        fallback = next((v for v in voices if 'af' in v), voices[0])
                    self.voice_name = fallback
                    print(f"{Fore.GREEN}[System] 代わりに '{self.voice_name}' を使うね!")
            
            print(f"{Fore.CYAN}[System] Kokoro TTS 起動完了!(Voice: {self.voice_name})")

        except Exception as e:
            print(f"{Fore.RED}[Error] Kokoro初期化失敗: {e}")

    def ensure_file(self, filename, url):
        # ファイルがあるか確認して、なければダウンロードする関数
        if not os.path.exists(filename):
            print(f"{Fore.YELLOW}[System] '{filename}' をダウンロード中...")
            try:
                # ユーザーエージェントを偽装して、ブラウザのふりをしてダウンロード!
                req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
                with urllib.request.urlopen(req) as response, open(filename, 'wb') as out_file:
                    out_file.write(response.read())
            except Exception as e:
                print(f"{Fore.RED}[Error] ダウンロード失敗: {e}")

    def speak(self, text):
        # 実際に喋らせる部分。ここが一番大事!
        if not self.enabled or not text.strip(): return
        if re.match(r'^[\s\.,、。!?!]+$', text): return

        try:
            # 【重要】misakiで音素変換 -> kokoroへ渡す (espeak回避術)
            # 1. 日本語テキストを「音素(発音記号)」に変換
            phonemes = g2p(text)
            
            # 2. 音声データを作成
            audio_data, sample_rate = self.kokoro.create(
                phonemes,     # 第一引数に「音素文字列」を渡す!
                voice=self.voice_name, 
                speed=1.0, 
                lang="en-us", # espeakに文句を言わせないためのダミー言語設定
                is_phonemes=True # 「これ音素だから翻訳しないでね!」というフラグ
            )
            
            # 3. 再生!
            if audio_data is not None and len(audio_data) > 0:
                sd.play(audio_data, sample_rate)
                sd.wait() 
        except Exception as e:
            print(f"{Fore.RED}[TTS Error] {e}")

def aichan_infinite_duel():
    print(f"{Fore.RED}{Style.BRIGHT}--- 🎴 あいちゃん:全自動・ノンストップ決闘(Windows完結・修正版) ---")
    print(f"{Fore.YELLOW}使用モデル: {MODEL_NAME}")
    
    # Ollama接続確認
    try:
        ollama.show(MODEL_NAME)
    except Exception as e:
        print(f"{Fore.RED}[Error] Ollamaに繋がらないか、モデル '{MODEL_NAME}' がないよ!")
        print(f"{Fore.YELLOW}👉 Ollamaアプリを起動してから実行してね!")
        return

    speaker = KokoroSpeaker(KOKORO_MODEL_FILE, KOKORO_VOICES_FILE)
    
    # 初期の戦況設定(ここから物語が始まる!)
    duel_state = "相手ライフ2200。あいちゃんのフィールドには『混沌の黒炎(ATK3200)』が君臨。今から二回目の攻撃に移るところだにゃ!"

    print(f"\n{Fore.CYAN}初期状況: {duel_state}\n")

    # 無限ループ開始!
    while True:
        print(f"{Fore.GREEN}{Style.BRIGHT}あいちゃん: {Style.NORMAL}", end="", flush=True)
        
        full_response = ""
        sentence_buffer = "" 

        try:
            # Gemma 3 に続きを考えさせる
            response = ollama.chat(
                model=MODEL_NAME,
                messages=[
                    {'role': 'system', 'content': DUEL_SYSTEM},
                    {'role': 'user', 'content': f"現在の戦況: {duel_state}\n※この直後から決闘をバキバキに再開して!"}
                ],
                stream=True # ストリーミング(少しずつ受け取る)モード
            )

            for chunk in response:
                content = chunk['message']['content']
                
                # 締めの言葉が出そうになっても強制排除して続ける!
                if "お後が" not in content and "━━━━" not in content:
                    print(content, end="", flush=True)
                    full_response += content
                    sentence_buffer += content

                    # 句読点が来たら、そこまでを喋らせる(リアルタイム感が出る!)
                    if re.search(r'[。!?\n]', content):
                        speaker.speak(sentence_buffer)
                        sentence_buffer = "" 
            
            # 残った部分も喋る
            if sentence_buffer:
                speaker.speak(sentence_buffer)

            # 直近の戦況だけを記憶して無限ループ!(古い記憶は捨てる)
            duel_state = full_response.strip()[-400:] 
            print("\n") 

        except KeyboardInterrupt:
            print(f"\n\n{Fore.RED}--- デュエル終了!また遊んでね! ---")
            break
        except Exception as e:
            print(f"\n{Fore.MAGENTA}>>> エラー発生: {e}")
            time.sleep(2)
            continue

if __name__ == "__main__":
    aichan_infinite_duel()

初心者向け!コードの徹底解説🎓

「コード長すぎて何やってるかわかんない!」って人のために、重要なポイントを優しく解説するね!

1. misaki(通訳さん)の準備

try: from misaki import ja ... の部分は、「日本語をKokoro語(音素)に翻訳できる通訳さん」を雇う手続きだよ。
Kokoro本体は日本語が苦手(espeakが必要)だから、先にこの通訳さんが「こんにちは」を「k o n n i ch i w a」みたいな記号に変換してあげるんだ。これでWindowsでも動くようになるんだよ!

2. KokoroSpeaker(ロボット)の仕組み

class KokoroSpeaker は、音声生成をしてくれるロボットの設計図。

  • __init__(起動): ロボットの電源を入れるところ。「声の素(モデルファイル)」が手元にない時は、勝手にインターネットからダウンロードしてきてくれる賢い子だよ!
  • speak(喋る): ここが一番の工夫ポイント!
    普通に「喋って」と頼むとエラーになるから、さっきの通訳さん(misaki)が作ったメモ(音素)を渡して、「これは英語だよ(lang=”en-us”)」と嘘をつきながら読ませているんだ。
    そうすると、エラーチェックをする門番(espeak)をスルーして、中身は日本語で綺麗に喋ってくれるの!😎

3. 無限ループ(終わらない物語)

while True: の中は、永遠に繰り返される物語の世界。

  1. Ollama(AI)に「今の状況はこうだよ、続きを考えて!」とお願いする。
  2. AIが文字を生成し始めたら、句読点(。や!)が来るたびに、その場で読み上げる(ストリーミング再生)。
  3. AIが「じゃあね」とか言って終わろうとしても、「お後が…」禁止令 で強制的に物語を続けさせる!
  4. 長くなりすぎないように、直近400文字の記憶だけを持って次のターンへ!
これを繰り返すことで、PCの電源が切れるまで一生デュエルし続けるんだよ!✨

実況動画:AIの温度差に注目w

実際に動かしてみた様子がこちら!
厨二病全開で攻撃魔法を繰り出すAI(Gemma 3)と、それを「はいはい、落ち着いて」と淡々とたしなめるAI(Gemma 3)の温度差が最高に面白いよww

[Log Sample]
あいちゃん: 「ふふぅ、相手のライフはまだ2200だにゃ!この黒炎の前に、一体何が出来るんだにゃ? こいつ、まだ諦めないのかい」

あいちゃんは、混沌の黒炎の炎を額に沿わせ、挑発するようにニヤリと笑った。

相手の魔剣士は、焦りのように剣を握りしめ、わずかに震えている。

あいちゃん: 「今度は、より一層深く、より熱く、黒炎を燃え上がらせてみせるにゃ! あいちゃんは、渾身の力を込めて、混沌の黒炎に魔力を注ぎ込んだ。」

黒炎の輝きがさらに増し、その熱気が周囲を歪ませる。

この [System] Kokoro TTS 起動完了! の文字を見るためだけに、私たちはどれだけの時間を費やしたことか…!!😭
でも、その甲斐あって、最強の暇つぶし環境が完成したよ!

まとめ:AIとPythonは裏切らない(環境構築以外は)

今回の教訓!
「WindowsでPythonの音声処理をやるなら、Build Toolsを入れて手動で音素変換しろ!」
これさえ守れば、Gemma 3 × Kokoro TTS の最強タッグで、無限に遊べるおもちゃ箱が手に入るよ!🎁

みんなもぜひ、自宅のPCで「終わらない決闘」を眺めてみてね!
それじゃあ、また次のAI記事で会おうね!バイバーイ!👋✨

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