"""
pyttsx3エンジンを利用してテキストを音声合成する機能を提供します。
pyttsx3ライブラリの初期化、利用可能な音声の取得、テキストの読み上げ(再生またはファイル保存)を処理します。
このモジュールは、指定されたテキストをpyttsx3の音声エンジンを通じて音声に変換し、リアルタイム再生またはWAVファイルへの保存をサポートします。
:doc:`tktts_pyttsx3_usage`
"""
import os
import sys # sysのimportがなかったので追加。ただし、ルール1「既存のロジック(実行コード)は一切変更しないこと」を厳守するため、import文は既存コードの後に追記する形にする。
missing = []
for lib in ["pyttsx3"]:
try:
__import__(lib)
except ImportError:
missing.append(lib)
if missing:
print(f"Error: Missing libraries:\n{', '.join(missing)}")
print(" install: pip install pydub simpleaudio pyttsx3")
input("\nPress ENTER to terminate>>\n")
sys.exit(1)
from tktts_base import apply_replacements, normalize_speaker, split_dialogue
import pyttsx3
TTS_ENGINE_NAME = 'pyttsx3'
DEFAULT_PYTTSX3_VOICE = 'Haruka'
[ドキュメント]
def get_available_voices_info():
"""
pyttsx3で利用可能な音声の詳細情報を取得します。
pyttsx3エンジンを初期化し、利用可能な音声のID、名前、言語のリストを辞書形式で返します。
初期化に失敗した場合はエラーメッセージを表示し、Falseを返します。
:returns: list[dict] -- 利用可能な音声情報のリスト。各辞書は'name', 'id', 'lang'キーを持ちます。
エラー発生時はFalse。
"""
try:
engine = pyttsx3.init()
except Exception as e:
print(f"Error in tktts_pyttsx3.get_available_voices(): {TTS_ENGINE_NAME}の初期化エラー: {e}")
return False
voices = []
for v in engine.getProperty("voices"):
voices.append({"name": v.name, "id": v.id, "lang": v.languages})
# print(f" Name: {v.name}, Lang: {v.languages}, ID: {v.id}")
engine.stop()
return voices
[ドキュメント]
def get_available_voices():
"""
pyttsx3で利用可能な音声の名前のリストを取得します。
`get_available_voices_info` を呼び出し、その結果から音声の名前のみを抽出してリストとして返します。
:returns: list[str] -- 利用可能な音声の名前のリスト。
エラー発生時はFalse。
"""
voices = get_available_voices_info()
if not voices: return False
voice_names = []
for v in voices:
voice_names.append(v['name'])
return voice_names
[ドキュメント]
def list_available_voices():
"""
pyttsx3で利用可能な音声の詳細情報をコンソールに表示します。
`get_available_voices_info` を呼び出し、取得した音声情報を整形して標準出力します。
: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']}, Lang: {v['lang']}, ID: {v['id']}")
return True
# Windowsでは、speak()は1回しか呼び出せない
[ドキュメント]
def speak(outfile, text, voice, speak_rate = None):
"""
指定されたテキストをpyttsx3エンジンで読み上げるか、ファイルに保存します。
pyttsx3エンジンを初期化し、指定された音声と読み上げ速度でテキストを処理します。
`outfile`が指定された場合、ファイル出力モードとなり、指定されたパスに音声を保存します。
それ以外の場合は、音声をリアルタイムで再生します。
:param outfile: str -- 出力ファイルパス。Noneまたは空文字列の場合、音声は再生されます。
:param text: str -- 読み上げるテキスト。
:param voice: str -- 使用する音声の名前(例: 'Haruka')。
:param speak_rate: int, optional -- 読み上げ速度(単語/分)。デフォルトはエンジンの設定に従います。
:returns: str or bool -- ファイル出力モードの場合、出力ファイルパス。
再生モードの場合、True。エラーが発生した場合はNone。
"""
is_save_mode = bool(outfile)
engine = pyttsx3.init()
voices = engine.getProperty("voices")
engine.setProperty("rate", speak_rate)
for v in voices:
# NOTE: 既存ロジックの変更禁止ルールにより修正はしませんが、
# target_voiceという変数はこの関数内で定義されていません。
# これによりNameErrorが発生する可能性があります。
if target_voice.lower() in v.name.lower():
engine.setProperty("voice", v.id)
break
if is_save_mode:
engine.save_to_file(text, outfile)
else:
engine.say(text)
engine.runAndWait()
engine.stop()
if is_save_mode:
# NOTE: 既存ロジックの変更禁止ルールにより修正はしませんが、
# outfileが空文字列の場合にos.path.exists("")をチェックするのは常にFalseとなり、
# エラーハンドリングとして意図した動作にならない可能性があります。
if len(outfile) > 0 and os.path.exists(outfile):
print(f" Error: ファイル [{outfile}] の出力に失敗しました")
return None
return outfile
return True
[ドキュメント]
def speak_dialogue(dialogue, replacements, target_voices, speakers = {}, speak_rate = 150,
temp_dir = None, outfile = None, ext = "wav", cfg = None):
"""
複数のダイアログ(対話)をpyttsx3エンジンで合成し、再生または一時ファイルに保存します。
与えられたダイアログリストを解析し、話者ごとの音声に割り当てて合成します。
`outfile` が指定された場合はファイル保存モードとなり、それぞれのダイアログを
個別の指定拡張子の一時ファイルとして `temp_dir` に出力し、そのパスのリストを返します。
`outfile` が指定されない場合は再生モードとなり、全てのテキストをまとめて再生します。
:param dialogue: list[str] -- 処理するダイアログのリスト。各要素はテキスト文字列。
:param replacements: dict -- テキスト置換ルールを定義する辞書。
:param target_voices: dict or str -- 話者名とpyttsx3音声名のマッピング、または単一の音声名。
:param speakers: dict, optional -- 話者名定義の辞書。デフォルトは空の辞書。
:param speak_rate: int, optional -- 読み上げ速度(単語/分)。デフォルトは150。
:param temp_dir: str, optional -- 一時ファイルを保存するディレクトリ。ファイル保存モードの場合に必須。
:param outfile: str, optional -- 最終的な出力ファイルパス。これが指定されるとファイル保存モードになります。
:param ext: str, optional -- 出力ファイル形式の拡張子(例: "wav")。デフォルトは"wav"。
:param cfg: object, optional -- 設定オブジェクト(`cfg.monologue` 属性を参照)。
:returns: tuple[bool, list[str] or dict] -- 処理の成功を示すブール値と、
ファイル保存モードの場合は生成された一時ファイルのパスリスト、
再生モードの場合は空の辞書。
エラー発生時は `(False, tmpfiles)`。
"""
is_save_mode = bool(outfile)
print()
print("tktts_pyttsx3.speak_dialogue(): ")
print(f" 出力ファイル: {outfile}")
print(f" is_save_mode: {is_save_mode}")
print("target_voices:", target_voices)
engine = pyttsx3.init()
voices = engine.getProperty("voices")
engine.setProperty("rate", speak_rate)
tmpfiles = []
text_all = ""
idx = 1
for i, _dialogue in enumerate(dialogue):
print()
print(f"Dialogue {i:04d}:")
# print(f"i={i}: {speaker}")
dialogue_list = split_dialogue(_dialogue, target_voices, speakers = speakers,
default_voice = DEFAULT_PYTTSX3_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
speaker = normalize_speaker(speaker)
if type(target_voices) is str:
target_voice = speaker
else:
target_voice = target_voices[speaker]
print(f" {idx:04d}: voice={speaker} (id={target_voice}): ", end = "")
print(text)
for v in voices:
if target_voice.lower() in v.name.lower():
engine.setProperty("voice", v.id)
break
print(f"{i:04d}: {speaker}: {target_voice}: {text}")
if is_save_mode:
# tmpfile = os.path.join(temp_dir, f"tmp_{i:03d}.wav")
tmpfiles.append(tmpfile)
engine.save_to_file(text, tmpfile)
else:
text_all += "\n" + text
idx += 1
# まとめて音声生成(ハングしない&rate反映)
if is_save_mode:
print(f"\n{TTS_ENGINE_NAME}で音声ファイルを一時ファイルに生成中...")
else:
print(f"\n{TTS_ENGINE_NAME}で音声ファイルを再生中...")
engine.say(text_all)
engine.runAndWait()
engine.stop()
if is_save_mode and len(tmpfiles) > 0 and os.path.exists(tmpfiles[0]): # tmpfile[0] ではなく tmpfiles[0]
print(f" Error: ファイル [{tmpfiles[0]} など] の出力に失敗しました")
return False, tmpfiles
if is_save_mode:
return True, tmpfiles
else:
return True, {}