"""
AquesTalkPlayerを利用したテキスト音声合成 (TTS) エンジンです。
このモジュールは、AquesTalkPlayer(Windowsアプリケーション)をsubprocess経由で呼び出し、
指定されたテキストを音声ファイルとして出力する機能を提供します。
複数の話者に対応し、対話形式のテキストを処理する `speak_dialogue` 関数を含みます。
関連リンク: :doc:`tktts_aquestalkplayer_usage`
"""
import os
import re
import time
import subprocess
from tktts_base import apply_replacements, normalize_speaker, split_dialogue
TTS_ENGINE_NAME = 'aquestalkplayer'
tts_model = "tts-1" # "tts-1-hd", "gpt-4o-mini-tts"
voices_available = ["れいむ", "まりさ", "こいし", "さとり", "男声1", "男声2", "女性1", "女性2",
"ロボット", "ロボット2", "機械"]
DEFAULT_ATP_VOICE = "れいむ"
[ドキュメント]
def get_available_voices_info():
"""
利用可能なAquesTalkPlayerの音声情報を取得します。
:returns: 利用可能な音声の名前を含む辞書のリスト。例: `[{"name": "れいむ"}, ...]`
:rtype: list[dict]
"""
voices = []
for v in voices_available:
voices.append({"name": v})
return voices
[ドキュメント]
def get_available_voices():
"""
利用可能なAquesTalkPlayerの音声名のリストを返します。
:returns: 利用可能な音声名のリスト。
:rtype: list[str]
"""
return voices_available
[ドキュメント]
def list_available_voices():
"""
利用可能なAquesTalkPlayerの音声をコンソールに一覧表示します。
:returns: 音声情報が正常に取得され表示された場合はTrue、それ以外はFalse。
:rtype: bool
"""
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']}")
return True
[ドキュメント]
def speak(outfile, text, voice, aquestalk_path = "aquestalkplayer.exe"):
"""
指定されたテキストをAquesTalkPlayerで音声合成し、ファイルに保存します。
AquesTalkPlayerの実行可能ファイル (`aquestalkplayer.exe`) をsubprocessとして呼び出し、
与えられたテキストと音声設定で音声ファイルを生成します。
処理の成功・失敗はコンソールに表示されます。
:param outfile: 出力する音声ファイルのパス。
:type outfile: str
:param text: 音声合成するテキスト。
:type text: str
:param voice: 使用する音声プリセット名(例: "れいむ", "男声1")。
:type voice: str
:param aquestalk_path: AquesTalkPlayer.exeへのパス。デフォルトはカレントディレクトリの `aquestalkplayer.exe`。
:type aquestalk_path: str
:returns: 音声ファイルが正常に保存された場合はそのパス、失敗した場合はNone。
:rtype: str or None
"""
if not os.path.isfile(aquestalk_path):
print(f"❌ AquesTalkPlayer.exe が見つかりません。パスを確認してください: {aquestalk_path}")
return None
cmd = [
aquestalk_path,
"/wait",
"/T", f'"{text}"',
"/P", voice,
"/W", f'"{outfile}"'
]
try:
# shell=True はセキュリティ上のリスクがあるが、既存プログラムの慣習に合わせて使用
result = subprocess.run(" ".join(cmd), shell=True, capture_output=True, text=True, encoding="shift_jis")
if result.returncode == 0:
print(f" ファイル [{outfile}] を保存しました")
else:
print(f" Error: ファイル [{outfile}] の出力に失敗しました")
print(f" ❌ AquesTalkPlayer 実行エラー? (Preset: {voice}, Text: {text[:20]}...)")
return None
except Exception as e:
print(f"❌ AquesTalkPlayer 実行中に予期せぬエラー: {e}")
return None
return outfile
[ドキュメント]
def speak_dialogue(dialogue, replacements, target_voices, speakers = {}, aquestalk_path = None,
temp_dir = None, outfile = None, ext = "wav", cfg = None):
"""
対話形式のテキストをAquesTalkPlayerで音声合成し、複数のテンポラリファイルに保存します。
与えられた対話リストを話者ごとに分割し、各テキストに対して `speak` 関数を呼び出して
個別の音声ファイルを生成します。テキストの置換処理も適用されます。
最終的な単一の `outfile` への結合は行わず、個別のテンポラリファイルをリストで返します。
:param dialogue: 対話のリスト。各要素は文字列または話者情報を含む辞書。
:type dialogue: list[str] or list[dict]
:param replacements: テキスト置換ルールを定義する辞書。
:type replacements: dict
:param target_voices: 話者名とAquesTalkPlayerの音声プリセット名のマッピング、または単一の音声プリセット名。
:type target_voices: dict or str
:param speakers: 話者IDと話者名のマッピング。デフォルトは空の辞書。
:type speakers: dict
:param aquestalk_path: AquesTalkPlayer.exeへのパス。指定がない場合はデフォルト値が使用される。
:type aquestalk_path: str or None
:param temp_dir: テンポラリ音声ファイルを保存するディレクトリのパス。
:type temp_dir: str or None
:param outfile: (未使用) 最終的に結合される出力ファイルのパス。この関数では個別のテンポラリファイルを生成するため、`None` を推奨。
:type outfile: str or None
:param ext: 出力する音声ファイルの拡張子。デフォルトは "wav"。実際のコード内では "wav" に固定されています。
:type ext: str
:param cfg: 設定オブジェクト。`cfg.monologue` でモノローグモードが指定される場合がある。
:type cfg: object or None
:returns: 成功フラグ (`True`または`False`) と、生成されたテンポラリ音声ファイルのパスのリスト。
:rtype: tuple[bool, list[str]]
"""
is_save_mode = bool(outfile)
print()
print("tktts_aquestalkplayer.speak_dialogue(): ")
tmpfiles = []
ext = "wav"
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_ATP_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:
target_voice = target_voices
else:
target_voice = target_voices[speaker]
print(f" {idx:04d}: voice={speaker} (id={target_voice}): ", end = "")
print(text)
tmpfile = os.path.join(temp_dir, f"tmp_{idx:03d}.{ext}")
_outfile = speak(tmpfile, text, voice = target_voice, aquestalk_path = aquestalk_path)
if _outfile is None:
return False, tmpfiles
tmpfiles.append(tmpfile)
idx += 1
return True, tmpfiles