ai.tktts_pyttsx3 のソースコード

"""
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, {}