ai.tktts_voicevox のソースコード

"""
tktts_voicevox.py - tktts用のVOICEVOXエンジン統合モジュール

このモジュールは、VOICEVOX REST APIを通じて音声合成を行う機能を提供します。
VOICEVOXエンジンと連携し、利用可能な話者の取得、テキストの音声合成、
およびダイアログ形式のテキスト処理に対応しています。

関連リンク: :doc:`tktts_voicevox_usage`
"""

import os
import sys
import re
import requests

missing = []
for lib in ["requests"]:
    try:
        __import__(lib)
    except ImportError:
        missing.append(lib)

if missing:
    print(f"Error: Missing libraries:\n{', '.join(missing)}")
    print("  install: pip install requests")
    input("\nPress ENTER to terminate>>\n")
    sys.exit(1)

from tktts_base import apply_replacements, normalize_speaker, split_dialogue


# VOICEVOXエンジンの設定
TTS_ENGINE_NAME = 'voicevox'
DEFAULT_VOICEVOX_VOICE = "四国めたん"
DEFAULT_ENDPOINT = "http://127.0.0.1:50021"  # エンジン起動時のデフォルトURL


[ドキュメント] def get_available_voices_info(endpoint=DEFAULT_ENDPOINT): """ VOICEVOXエンジンから利用可能な話者情報(名前、スタイル、ID)のリストを取得します。 詳細説明: VOICEVOXエンジンの `/speakers` エンドポイントにGETリクエストを送信し、その応答をJSON形式で解析します。 各話者のスタイル情報も抽出して、扱いやすい辞書のリストとして返します。 :param endpoint: str: VOICEVOXエンジンのAPIエンドポイントURL。 :returns: list[dict]: 話者情報を含む辞書のリスト。各辞書は'name', 'style', 'id'キーを持ちます。 """ response = requests.get(f"{endpoint}/speakers") response.raise_for_status() speakers = response.json() speakers_dict = [] voices = [] for sp in speakers: name = sp["name"] for style in sp["styles"]: # print(f"{name} - {style['name']} (id={style['id']})") # speakers_dict.append(sp) voices.append({"name": name, "style": style['name'], "id": style['id']}) return voices
[ドキュメント] def get_available_voices(endpoint=DEFAULT_ENDPOINT): """ 利用可能なVOICEVOX話者のフルネーム(名前とスタイル)のリストを取得します。 詳細説明: `get_available_voices_info` 関数を呼び出して詳細な話者情報を取得し、 それらを「話者名(スタイル名)」の形式の文字列リストに変換します。 :param endpoint: str: VOICEVOXエンジンのAPIエンドポイントURL。 :returns: list[str] or bool: 利用可能な話者名のリスト。話者が見つからない場合はFalse。 """ voices = get_available_voices_info(endpoint) if not voices: return False voice_names = [] for v in voices: voice_names.append(f"{v['name']}{v['style']})") return voice_names
[ドキュメント] def list_available_voices(endpoint=DEFAULT_ENDPOINT): """ 利用可能なVOICEVOX話者の一覧をコンソールに表示します。 詳細説明: `get_available_voices_info` 関数を使用して話者情報を取得し、 話者の名前、スタイル、IDを整形して標準出力に表示します。 :param endpoint: str: VOICEVOXエンジンのAPIエンドポイントURL。 :returns: bool: 話者の一覧表示が成功した場合はTrue、話者が見つからない場合はFalse。 """ print(f"=== 利用可能な {TTS_ENGINE_NAME} voices ===") voices = get_available_voices_info() if not voices: return False for v in voices: print(f" Name: {v['name']}, Style: {v['style']}, ID: {v['id']}") return True
[ドキュメント] def resolve_speaker_id(speaker_name, endpoint=DEFAULT_ENDPOINT, speakers_dict = None): """ 話者名からVOICEVOXのspeaker_idを解決します。 詳細説明: 渡された`speaker_name`(フルネームまたは部分文字列)に一致する最初の話者のspeaker_idを検索して返します。 大文字小文字を区別しない検索を行います。`speakers_dict`がNoneの場合、 `get_available_voices_info`を呼び出して話者情報を取得します。 :param speaker_name: str: 検索する話者の名前またはスタイルの一部。 :param endpoint: str: VOICEVOXエンジンのAPIエンドポイントURL。 :param speakers_dict: list[dict] or None: 事前に取得した話者情報の辞書リスト。Noneの場合、内部で取得します。 :returns: tuple[list[dict], int]: 更新された話者情報の辞書リストと、見つかったspeaker_id。 :raises ValueError: 指定された話者が見つからない場合。 """ if speakers_dict is None: speakers_dict = get_available_voices_info(endpoint) speaker_name = normalize_speaker(speaker_name) #.lower()) # print(f" search for [{speaker_name}]") # print(f" speakers_dict:", speakers_dict) for voice in speakers_dict: full_name = voice['name'].lower() style = voice['style'].lower() if speaker_name in full_name or speaker_name in style: return speakers_dict, voice["id"] raise ValueError(f"❌ Error in tktts_voicevox.resolve_speaker_id(): 話者 [{speaker_name}] が見つかりませんでした")
[ドキュメント] def speak(outfile, text, voice, speak_rate = None, speak_pitch = None, endpoint = DEFAULT_ENDPOINT): """ 指定されたテキストをVOICEVOXエンジンで音声合成し、WAVファイルとして保存します。 詳細説明: まず`/audio_query`エンドポイントで音声合成クエリを作成し、 そのクエリに対して話速(`speedScale`)や音高(`pitchScale`)を適用します。 次に`/synthesis`エンドポイントで実際の音声データを生成し、 指定されたファイルパスにバイナリ形式で書き込みます。 :param outfile: str: 音声データが保存されるファイルパス。 :param text: str: 合成するテキスト。 :param voice: int: 使用するVOICEVOX話者のspeaker_id。 :param speak_rate: float or None: 音声の再生速度スケール。Noneの場合はデフォルト値を使用。 :param speak_pitch: float or None: 音声のピッチスケール。Noneの場合はデフォルト値を使用。 :param endpoint: str: VOICEVOXエンジンのAPIエンドポイントURL。 :returns: str or None: 保存されたファイルパス。エラーが発生した場合はNone。 """ query = requests.post( f"{endpoint}/audio_query", params={"text": text, "speaker": voice} ) query.raise_for_status() query_json = query.json() if speak_rate is not None: query_json["speedScale"] = speak_rate if speak_pitch is not None: query_json["pitchScale"] = speak_pitch try: synthesis = requests.post( f"{endpoint}/synthesis", params={"speaker": voice}, json=query_json ) synthesis.raise_for_status() except requests.exceptions.RequestException as e: print(f"❌ VOICEVOX 接続エラー: {e}") return None with open(outfile, "wb") as f: f.write(synthesis.content) if os.path.exists(outfile): print(f" ** 一時ファイル [{outfile}] を保存しました") else: print(f" ** Error: ファイル [{outfile}] の出力に失敗しました") return None return outfile
# target_voicesが文字列型の場合、speakerを強制的に置き換える
[ドキュメント] def speak_dialogue(dialogue, replacements, target_voices, speakers = {}, temp_dir=".", outfile=None, ext="wav", endpoint=DEFAULT_ENDPOINT, cfg = None): """ ダイアログリストの各セグメントをVOICEVOXで音声合成し、一時ファイルに保存します。 詳細説明: 入力された`dialogue`(通常は複数の会話セグメントのリスト)を`split_dialogue`関数で個々の発言に分割します。 各発言について、話者名からspeaker_idを解決し、`speak`関数を呼び出して音声合成を行います。 生成された一時ファイルのパスをリストとして返します。 `target_voices`が文字列の場合、全てのセグメントでその話者が強制的に使用されます。 :param dialogue: list[str]: 会話テキストのリスト。 :param replacements: dict: テキスト置換ルールを定義する辞書。 :param target_voices: str or list[str]: 使用する話者。文字列の場合、全てのセグメントでその話者が強制されます。リストの場合、`split_dialogue`が話者を決定します。 :param speakers: dict: 話者ごとの追加設定を含む辞書。 :param temp_dir: str: 一時ファイルを保存するディレクトリパス。 :param outfile: str or None: 最終的な出力ファイルパス(保存モードの場合)。Noneの場合、一時ファイルを生成します。 :param ext: str: 生成される音声ファイルの拡張子(例: 'wav')。 :param endpoint: str: VOICEVOXエンジンのAPIエンドポイントURL。 :param cfg: object or None: 設定オブジェクト。`fspeak_rate`や`fspeak_pitch`などの属性を持つ場合があります。 :returns: tuple[bool, list[str]]: 処理が成功したかを示す真偽値と、生成された一時ファイルのパスリスト。 """ is_save_mode = bool(outfile) # print() print("tktts_voicevox.speak_dialogue(): target_voices:", target_voices) tmpfiles = [] speakers_dict = None # 同じspekaerを何度も検索しなくて済むようにglobalな辞書を作る idx = 1 for i, _dialogue in enumerate(dialogue): print() print(f"Dialogue {i:04d}:") dialogue_list = split_dialogue(_dialogue, target_voices, speakers = speakers, default_voice = DEFAULT_VOICEVOX_VOICE, is_monologue = cfg.monologue) for speaker, text in dialogue_list: tmpfile = os.path.join(temp_dir, f"tmp_{idx:03d}.{ext}") text = apply_replacements(text, replacements) if type(target_voices) is str: speaker = target_voices try: speakers_dict, target_voice = resolve_speaker_id(speaker, endpoint, speakers_dict = speakers_dict) except ValueError as e: print(e) return False, tmpfiles print(f" {idx:04d}: voice={speaker} (id={target_voice}): ", end = "") print(text) speedScale = cfg.fspeak_rate if hasattr(cfg, 'fspeak_rate') else None pitchScale = cfg.fspeak_pitch if hasattr(cfg, 'fspeak_pitch') else None _outfile = speak(outfile = tmpfile,text = text, voice = target_voice, speak_rate = speedScale, speak_pitch = pitchScale, endpoint = endpoint) if _outfile is None: return False, tmpfiles tmpfiles.append(_outfile) idx += 1 return True, tmpfiles