speak.py 技術ドキュメント

プログラムの動作

speak.py は、複数のText-to-Speech(TTS)エンジンを統合し、テキストデータから音声を生成・再生、または音声ファイルとして出力するためのコマンドラインインターフェースツールです。主な目的は、スクリプトや対話形式のテキスト(例: YouTube動画の台本、物語の朗読)を、話者ごとに異なる声で効率的に読み上げることです。

このプログラムは以下の機能を提供します。

  • 複数のTTSエンジンサポート: pyttsx3(システム標準TTS)、VOICEVOX(HTTP API経由)、AquesTalkPlayer(外部プログラム)、OpenAI TTS API(クラウドサービス)のいずれかを選択して利用できます。

  • 柔軟な入力ソース: クリップボードから直接テキストを取得するか、指定されたファイルからテキストを読み込みます。

  • 対話形式の解析: コロン : で区切られた「話者名: セリフ」形式のテキストを解析し、話者ごとに適切な音声を選択して読み上げます。独話形式のテキストもサポートしています。

  • 音声マッピングのカスタマイズ: 事前定義された話者と音声のマップを、コマンドライン引数で上書きしてカスタマイズできます。

  • 文字列置換: 読み上げ前にテキスト内の特定の文字列を置換するルールを適用できます。

  • 音声出力オプション: 音声をリアルタイムで再生するだけでなく、結合された単一の音声ファイル(例: WAV, MP3)として出力することも可能です。

  • 読み上げパラメータの調整: 読み上げ速度、ピッチ、音声間の無音区間などをエンジンごとに細かく設定できます。

  • ユーティリティ機能: 利用可能な音声リストの表示や、現在の音声マップの表示が可能です。

これにより、ユーザーは用途に応じて最適なTTSエンジンを選択し、キャラクターごとの声を簡単に割り当てて、柔軟な音声コンテンツを生成できます。

原理

speak.py は、argparse モジュールを使用してコマンドライン引数を解析し、ユーザーの指定に基づいてTTS処理を実行します。プログラムの主要な処理フローとアルゴリズムは以下の通りです。

  1. 初期設定と引数解析:

    • プログラム起動時、initialize() 関数で argparse を用いてコマンドライン引数を解析し、実行設定(使用TTSエンジン、入力元、出力先、各種パラメータなど)を取得します。

    • 必要な非標準ライブラリ(chardet, pyperclip, tktts)が利用可能かを確認し、不足していればエラーメッセージを表示して終了します。

  2. tkTTS オブジェクトの初期化:

    • 解析された引数を基に、tktts モジュール内の tkTTS クラスのインスタンスを生成します。この tkTTS オブジェクトが、選択されたTTSエンジン(pyttsx3, voicevox, aquestalkplayer, openai)の具体的な操作を抽象化し、提供します。

  3. テキストの読み込みと解析:

    • tktts.load_text() メソッドが、--infile 引数で指定された入力元(ファイルパスまたはクリップボード"clip")からテキストを読み込みます。

      • ファイルの場合、chardet を使用してエンコーディングを自動検出します。

      • クリップボードの場合、pyperclip を使用して内容を取得します。

    • 読み込んだテキストは行ごとに処理され、--monologue オプションの状態に応じて対話形式(話者名: セリフ)または独話形式として解析されます。対話形式の場合、コロンで区切られた部分から話者名とセリフが抽出され、リスト形式の dialogue オブジェクトとして格納されます。

  4. 話者の検出と音声マッピング:

    • tktts.get_speakers_from_dialogue() メソッドにより、解析された dialogue からすべての一意な話者名が抽出されます。

    • tktts.update_voice_map() メソッドにより、組み込みの VOICE_MAPS 辞書と、--voices 引数で指定されたカスタムマッピングを結合・更新し、最終的な話者とTTSエンジン固有の音声の対応付け (current_voice_map) を構築します。

      • VOICE_MAPS は、"四国めたん"などの抽象的なキャラクター名を、各TTSエンジンが持つ具体的な音声ID(例: pyttsx3の"Zira"、OpenAIの"nova")に変換する役割を担います。

  5. 文字列置換ルールの適用:

    • --replace 引数で指定された「key=val;key=val」形式の文字列置換ルールを tktts.parse_kv_string() で解析し、辞書形式で保持します。

    • 読み上げ時には、各セリフにこれらの置換ルールが適用されます。

  6. 音声生成と出力:

    • tktts.speak_dialogue() メソッドが、dialoguecurrent_voice_mapreplacements、およびその他の設定 (config) を使用して、実際の音声生成と出力を行います。

    • 各セリフについて、対応する話者の音声が current_voice_map から決定されます。

    • 選択されたTTSエンジンに応じて、以下のいずれかの方法で音声が生成されます。

      • pyttsx3: pyttsx3 ライブラリの engine.say() および engine.runAndWait() メソッドを通じて、システムのTTSエンジンを直接制御します。

      • VOICEVOX: --endpoint で指定されたVOICEVOX EngineのHTTP APIに対して、テキスト、話者ID、速度、ピッチなどのパラメータを含むHTTP POSTリクエストを送信し、音声データ(WAV形式)を取得します。

      • AquesTalkPlayer: --aquestalk_path で指定された AquesTalkPlayer.exe をsubprocessとして呼び出し、テキストを引数として渡します。生成された一時WAVファイルを読み込みます。

      • OpenAI TTS API: openai ライブラリを通じて、OpenAIのクラウドTTS APIにテキスト、モデル名、音声名、追加指示などのパラメータを送信し、音声データ(MP3またはWAV形式)を取得します。

    • ファイル出力 (--outfile が指定された場合):

      • AquesTalkPlayer および OpenAI の場合、生成された個々の音声ファイル(WAV形式)は、--temp_dir で指定された一時ディレクトリに保存されます。

      • pydub ライブラリを使用して、これらの個別の音声ファイルを指定された --tinterval(無音区間)で結合し、最終的に --outfile で指定されたファイル名と形式で保存します。

      • pyttsx3 および VOICEVOX も、pydub を介して一時ファイルを結合・保存する可能性があります(tktts の内部実装に依存)。

    • リアルタイム再生 (--outfile が未指定の場合):

      • 各TTSエンジンが生成した音声を直接再生します。pyttsx3 はOSの再生機能を利用し、その他のエンジンは生成した音声データをストリーム再生するか、一時ファイル再生機能を利用します。

  7. 終了処理:

    • 処理が完了すると、「--- 処理完了 ---」のメッセージが表示され、--pause オプションが設定されていれば、ユーザー入力待ちとなります。

このプログラムは、抽象化レイヤー(tkTTS クラス)を導入することで、異なるTTSエンジンの複雑な呼び出し方や設定の違いを吸収し、ユーザーに対して統一されたインターフェースを提供しています。

必要な非標準ライブラリとインストール方法

speak.py を実行するには、以下のPython非標準ライブラリが必要です。これらは pip コマンドを使用してインストールできます。

  • chardet: テキストファイルのエンコーディングを自動検出するために使用されます。

  • pyttsx3: オペレーティングシステムのTTSエンジン(SAPI5, NSSpeechSynthesizer, eSpeakなど)と連携するために使用されます。

  • openai: OpenAIのAPI(この場合はTTS API)と連携するために使用されます。

  • pydub: 音声ファイルの結合や変換(特にAquesTalkPlayerやOpenAI使用時)に使用されます。

  • pyperclip: クリップボードの内容を読み書きするために使用されます。

  • tktts モジュール: このプログラムのTTSエンジンラッパーとして機能します。注意: tktts はPyPIで提供されている一般的なライブラリではないため、speak.py と同じディレクトリに tktts.py ファイルとして配置されるか、Pythonの検索パスに存在する必要があります。

pip install chardet pyttsx3 openai pydub pyperclip

外部依存:

  • ffmpeg.exe: pydub が音声ファイルの結合や変換を行うために必要です。環境PATHに設定するか、pydub が検出できる場所に配置する必要があります。

  • AquesTalkPlayer.exe: AquesTalkPlayer エンジンを使用する場合に必要です。Windowsでのみ利用可能であり、--aquestalk_path 引数でその実行パスを指定する必要があります。

  • OpenAI API Key: openai エンジンを使用する場合に必要です。環境変数 OPENAI_API_KEY に設定しておく必要があります。

# OpenAI API Key の設定例 (Linux/macOS)
export OPENAI_API_KEY="YOUR_OPENAI_API_KEY"

# OpenAI API Key の設定例 (Windows コマンドプロンプト)
set OPENAI_API_KEY="YOUR_OPENAI_API_KEY"

# OpenAI API Key の設定例 (Windows PowerShell)
$env:OPENAI_API_KEY="YOUR_OPENAI_API_KEY"

必要な入力ファイル

speak.py は、以下の形式で入力テキストを処理します。

  • 入力元: --infile オプションで指定します。

    • "clip" (デフォルト): クリップボードからテキストを読み込みます。

    • ファイルパス: 指定されたテキストファイルからテキストを読み込みます。

  • ファイル形式: UTF-8などの一般的なテキストファイル(.txtなど)を想定しています。chardet ライブラリにより、エンコーディングの自動検出が試みられます。

  • データ構造:

    • 対話形式 (デフォルト、--monologue=0): 各行が「話者名: セリフ」の形式で記述されていることを期待します。話者名とセリフはコロン : で区切られます。

      四国めたん: こんにちは、四国めたんです。
      ずんだもん: ずんだもんもいるのだ。
      れいむ: あなたは誰?
      
    • 独話形式 (--monologue=1): 各行がセリフとして扱われます。話者名は指定されず、内部的にデフォルトの話者(または最初の話者)が割り当てられるか、TTSエンジン自体が話者を必要としないモードで動作します。

      これは独話形式のサンプルテキストです。
      すべての行が順番に読み上げられます。
      話者を指定する必要はありません。
      

      対話形式のファイルでも、コロンを含まない行は独話として扱われる場合があります。

生成される出力ファイル

speak.py は、--outfile オプションの指定に応じて、以下の出力を行います。

  1. 音声ファイル (--outfile が指定された場合):

    • 指定されたパスとファイル名で、音声ファイルが保存されます。

    • pydub ライブラリの機能により、.wav, .mp3 などの一般的な音声フォーマットをサポートします。拡張子に応じて自動的にフォーマットが選択されます。

    • 例: --outfile output.mp3 と指定すると、output.mp3 という名前のMP3ファイルが生成されます。

    • ファイルの内容は、読み上げられたすべてのセリフが、指定された無音区間 (--tinterval) を挟んで結合されたものになります。

  2. 一時音声ファイル (AquesTalkPlayer または OpenAI 使用時):

    • --temp_dir オプションで指定されたディレクトリ(デフォルトは tts_temp_wavs)内に、個々のセリフに対応する一時的な .wav ファイルが生成されます。

    • これらのファイルは、最終的な音声ファイルに結合された後、通常はプログラムの終了時に削除されますが、エラー発生時などに残る可能性があります。

  3. 標準出力:

    • プログラムの進行状況、選択された設定、検出された話者、エラーメッセージ、警告などがコンソール(標準出力)に出力されます。

    • --list オプションや --map オプションが指定された場合は、利用可能な音声リストや現在の音声マップが標準出力に表示されて終了します。

コマンドラインでの使用例 (Usage)

speak.py は以下の基本的な形式で実行できます。

python speak.py [OPTIONS]

利用可能な主なオプションは以下の通りです。

  • --tts <engine>, -t <engine>: 使用するTTSエンジンを選択します。

    • choices: pyttsx3, voicevox, aquestalkplayer, atp, openai

    • default: pyttsx3

  • --endpoint <url>: VOICEVOX EngineのAPIエンドポイントURLを指定します。(VOICEVOX使用時)

    • default: http://127.0.0.1:50021

  • --monologue <0|1>, -m <0|1>: 独話形式でテキストを読み込むかを指定します。

    • 0: 対話形式 (デフォルト)

    • 1: 独話形式 (カンマのない行も読み込む)

    • default: 0

  • --voices <map_str>, -v <map_str>: voice_map を上書きします。key=val;key=val 形式で指定します。

  • --replace <rule_str>, -r <rule_str>: 文字列置換ルールを指定します。key=val;key=val 形式で指定します。

  • --infile <source>, -i <source>: 入力元を指定します。'clip' (クリップボード) またはファイルパス。

    • default: 'clip'

  • --outfile <file_path>, -o <file_path>: 出力音声ファイルのパスを指定します。未指定の場合、リアルタイム再生されます。

  • --temp_dir <dir_name>: 一時ファイルを作成するディレクトリ名を指定します。(AquesTalkPlayer/OpenAI使用時)

    • default: tts_temp_wavs

  • --list: 利用可能なvoicesを表示して終了します。

  • --map: 現在のvoice mapを表示して終了します。

  • --pause <0|1>, -p <0|1>: 終了時に入力待ちするかを指定します。

    • default: 0

  • --wait_for_clipboard <0|1>: クリップボードからテキストを取得する際に入力待ちするかを指定します。

    • default: 1

  • --speak_rate <rate>: pyttsx3 の読み上げ速度 (Word Per Minute) を指定します。

    • default: 150

  • --fspeak_rate <rate_ratio>: VOICEVOX の読み上げ速度比を指定します。

    • default: 1.0

  • --fspeak_pitch <pitch_val>: VOICEVOX の声の高さの変化量を指定します。

    • default: 0.0

  • --aquestalk_path <path>: AquesTalkPlayer.exe の実行パスを指定します。(AquesTalkPlayer使用時)

    • default: AquesTalkPlayer.exe

  • --tinterval <seconds>: 音声ファイル間に挿入する無音区間の長さ(秒)を指定します。(AquesTalkPlayer/OpenAI使用時)

    • default: 0.5

  • --instruction <text>: OpenAI TTS APIへの追加指示を指定します。(OpenAI使用時)

コマンドラインでの具体的な使用例

ここでは、speak.py の具体的な使用例をいくつか紹介します。以下の例では、入力ファイルとして dialogue.txt を想定します。

dialogue.txt の内容:

四国めたん: こんにちは、四国めたんです。
ずんだもん: ずんだもんもいるのだ。
れいむ: これはテストです。

1. クリップボードのテキストを pyttsx3 でリアルタイム再生 (デフォルト設定)

事前にクリップボードに上記の dialogue.txt の内容をコピーしておき、以下のコマンドを実行します。

python speak.py

実行結果の説明: デフォルトの pyttsx3 エンジンを使用し、クリップボードの内容が自動的に読み込まれます。 VOICE_MAPS に基づいて、「四国めたん」は "Zira"(女性声)、「ずんだもん」は "David"(男性声)、「れいむ」は "Zira"(女性声)にマッピングされ、それぞれのセリフがシステムのスピーカーから読み上げられます。


2. ファイルから OpenAI TTS API を使用してMP3ファイルに出力

dialogue.txt を入力ファイルとして使用し、OpenAI TTS APIで音声を生成、output.mp3 というファイルに出力します。

python speak.py -t openai -i dialogue.txt -o output.mp3 -v "れいむ=alloy;まりさ=fable"

実行結果の説明: openai エンジンが使用され、dialogue.txt の内容が読み込まれます。 --voices オプションにより、"れいむ"には"alloy"、"まりさ"には"fable"というOpenAIの音声が明示的に割り当てられます。 「四国めたん」はVOICE_MAPSから"nova"、「ずんだもん」は"shimmer"にマッピングされます。 生成された音声は output.mp3 というファイルに保存され、各話者のセリフの間に0.5秒の無音区間が挿入されます。


3. VOICEVOX を使用し、速度とピッチを調整してリアルタイム再生

ローカルでVOICEVOX Engineが起動していることを前提とします (http://127.0.0.1:50021 でAPIが利用可能)。

python speak.py -t voicevox -i dialogue.txt --fspeak_rate 1.2 --fspeak_pitch 0.1

実行結果の説明: voicevox エンジンが使用され、dialogue.txt の内容が読み込まれます。 読み上げ速度が標準の1.0から1.2倍に、ピッチが標準の0.0から0.1だけ高めに調整されます。 VOICE_MAPS に基づいてVOICEVOXの話者IDが決定され、リアルタイムで音声が再生されます。


4. 利用可能な pyttsx3 の音声リストを表示

python speak.py -t pyttsx3 --list

実行結果の説明: pyttsx3 エンジンが利用可能なシステムにインストールされているすべての音声(Voice IDや名前など)がコンソールに一覧表示され、プログラムは終了します。


5. 現在の音声マップを表示

dialogue.txt を入力ファイルとして、現在の音声マップを表示します。

python speak.py -i dialogue.txt --map

実行結果の説明: dialogue.txt から検出された話者と、現在のTTSエンジン(デフォルトは pyttsx3)に対する VOICE_MAPS のマッピングがコンソールに表示され、プログラムは終了します。--voices オプションでカスタムマッピングを指定している場合は、それも反映されたマップが表示されます。 例えば、pyttsx3 の場合、以下のような出力の一部が表示されることがあります(環境によって異なる)。

Voice map updated:
  (speaker) 四国めたん: (voice) Zira
  (speaker) ずんだもん: (voice) David
  (speaker) れいむ: (voice) Zira
=== 検出された話者とvoice ===
Voice map updated; {'四国めたん': 'Zira', 'ずんだもん': 'David', 'れいむ': 'Zira'}
  (speaker) ずんだもん: (voice) David
  (speaker) れいむ: (voice) Zira
  (speaker) 四国めたん: (voice) Zira