初心者向け No.40

音声合成アプリ

入力したテキストを読み上げる音声合成アプリ。pyttsx3ライブラリの使い方を学びます。

🎯 難易度: ★★☆ 📦 ライブラリ: tkinter(標準ライブラリ), pyttsx3 ⏱️ 制作時間: 30〜90分

1. アプリ概要

入力したテキストを読み上げる音声合成アプリ。pyttsx3ライブラリの使い方を学びます。

このアプリはツールカテゴリに分類される実践的なGUIアプリです。使用ライブラリは tkinter(標準ライブラリ)・pyttsx3 で、難易度は ★★☆ です。

このアプリは「ツール」カテゴリです。日々の作業を自動化する実用ツールは Python が最も得意とする領域です。短いコードで実用性のある成果物が作れる点が魅力です。tkinter(標準ライブラリ)・pyttsx3 を活かして実装するこの構造は、他のアプリにも応用が効きます。

動かしながら読むことが理解の最短経路です。まずはコードをコピーして実行し、想定どおりに動くことを確認したうえで解説と照らし合わせてください。

カスタマイズでは「機能追加」「UI 改善」「エラー耐性」の三方向で考えると視野が広がります。練習問題にもそれぞれの方向の具体例を用意しています。

音声合成アプリ 実行画面(Windows)
実行画面(Windows)
音声合成アプリ 実行画面(Linux Mint)
実行画面(Linux Mint)

2. 機能一覧

  • 「▶ 読み上げる」ボタン(speak())・「停止」ボタン(stop())で操作
  • messagebox.showwarning() によるダイアログ通知
  • ウィンドウはタイトル「音声合成アプリ」・サイズ 500x400 で起動

3. 事前準備・環境

ℹ️
動作確認環境

Python 3.10 以上 / Windows 11(実機)で起動・基本操作を確認 / Linux Mint 22.3(仮想マシン)で起動・画面表示を確認(macOS は未検証)

Windows 11(実機)で起動と基本操作を確認しています(全機能の網羅テストではありません)。Linux Mint 22.3(仮想マシン)では起動と画面表示を確認しました。macOS は必要なライブラリ(pip install)を入れれば動作する想定ですが、未検証です。

  • Python 3.10 以上
  • OS: Windows 11(実機で起動・基本操作を確認)・Linux Mint 22.3(仮想マシンで起動・画面表示を確認)

インストールが必要なライブラリ

pip install pyttsx3

4. 完全なソースコード

💡
コードのコピー方法

右上の「コピー」ボタンをクリックするとコードをクリップボードにコピーできます。

📦 必要なライブラリをインストール(初回のみ)
pip install pyttsx3
app40.py
import tkinter as tk
from tkinter import ttk, messagebox
import threading


class App40:
    """音声合成アプリ"""

    def __init__(self, root):
        self.root = root
        self.root.title("音声合成アプリ")
        self.root.geometry("500x400")
        self.root.configure(bg="#f8f9fc")
        self.engine = None
        self._init_tts()
        self._build_ui()

    def _init_tts(self):
        try:
            import pyttsx3
            self.engine = pyttsx3.init()
            self.tts_available = True
        except Exception:
            self.tts_available = False

    def _build_ui(self):
        title_frame = tk.Frame(self.root, bg="#3776ab", pady=12)
        title_frame.pack(fill=tk.X)
        tk.Label(title_frame, text="音声合成アプリ",
                 font=("Noto Sans JP", 16, "bold"),
                 bg="#3776ab", fg="white").pack()

        main_frame = tk.Frame(self.root, bg="#f8f9fc", padx=24, pady=16)
        main_frame.pack(fill=tk.BOTH, expand=True)

        if not self.tts_available:
            tk.Label(main_frame,
                     text="⚠ pyttsx3 がインストールされていません\n\npip install pyttsx3\n\nを実行してからもう一度起動してください",
                     bg="#fff3cd", fg="#856404", font=("Noto Sans JP", 12),
                     relief=tk.SOLID, bd=1, padx=16, pady=16,
                     justify="center").pack(fill=tk.X)
            return

        # テキスト入力
        input_frame = ttk.LabelFrame(main_frame, text="読み上げるテキスト", padding=10)
        input_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
        self.text_widget = tk.Text(input_frame, font=("Noto Sans JP", 12),
                                   height=6, wrap=tk.WORD, padx=6, pady=6)
        self.text_widget.insert("1.0", "こんにちは!Pythonの音声合成アプリです。")
        self.text_widget.pack(fill=tk.BOTH, expand=True)

        # 設定
        setting_frame = ttk.LabelFrame(main_frame, text="設定", padding=8)
        setting_frame.pack(fill=tk.X, pady=(0, 10))

        tk.Label(setting_frame, text="速度:").grid(row=0, column=0, sticky="w")
        self.rate_var = tk.IntVar(value=150)
        ttk.Scale(setting_frame, variable=self.rate_var, from_=50, to=300,
                  orient=tk.HORIZONTAL, length=160).grid(row=0, column=1, padx=8)
        self.rate_label = tk.Label(setting_frame, text="150", bg=self.root.cget("bg"), width=4)
        self.rate_label.grid(row=0, column=2)
        self.rate_var.trace("w", lambda *a: self.rate_label.config(text=str(self.rate_var.get())))

        tk.Label(setting_frame, text="音量:").grid(row=1, column=0, sticky="w", pady=4)
        self.vol_var = tk.DoubleVar(value=1.0)
        ttk.Scale(setting_frame, variable=self.vol_var, from_=0.0, to=1.0,
                  orient=tk.HORIZONTAL, length=160).grid(row=1, column=1, padx=8)

        # 操作ボタン
        btn_frame = tk.Frame(main_frame, bg="#f8f9fc")
        btn_frame.pack()
        tk.Button(btn_frame, text="▶ 読み上げる",
                  font=("Noto Sans JP", 13, "bold"),
                  bg="#3776ab", fg="white", activebackground="#2a5a8a",
                  relief=tk.FLAT, padx=16, pady=8,
                  command=self.speak).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_frame, text="停止", command=self.stop).pack(side=tk.LEFT)

        self.status_label = tk.Label(main_frame, text="",
                                     bg="#f8f9fc", font=("Noto Sans JP", 10), fg="#555")
        self.status_label.pack(pady=(8, 0))

    def speak(self):
        if not self.tts_available:
            return
        text = self.text_widget.get("1.0", tk.END).strip()
        if not text:
            messagebox.showwarning("警告", "テキストを入力してください")
            return

        self.engine.setProperty("rate", self.rate_var.get())
        self.engine.setProperty("volume", self.vol_var.get())
        self.status_label.config(text="🔊 読み上げ中...", fg="#3776ab")

        def _run():
            self.engine.say(text)
            self.engine.runAndWait()
            self.root.after(0, lambda: self.status_label.config(text="✅ 完了", fg="#27ae60"))

        threading.Thread(target=_run, daemon=True).start()

    def stop(self):
        if self.tts_available and self.engine:
            self.engine.stop()
            self.status_label.config(text="⏹ 停止", fg="#e74c3c")


if __name__ == "__main__":
    root = tk.Tk()
    app = App40(root)
    root.mainloop()

5. コード解説

音声合成アプリのコードを、実際に書かれている実装に沿って解説します。

クラス設計とコンストラクタ

App40 クラスにアプリの全機能をまとめています(メソッド5個・全111行)。__init__ ではタイトル「音声合成アプリ」とウィンドウサイズ 500x400 を設定します。あわせて状態を保持する変数(self.engine)を初期化し、そのあと _init_tts()_build_ui() の順に呼び出して初期状態を作ります。

※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。

ウィジェット構成

画面は tk.Frame×3・tk.Label×6・ttk.LabelFrame×2・tk.Textttk.Scale×2・tk.Buttonttk.Button で構成しています。見出し付きの枠(LabelFrame)は「読み上げるテキスト」「設定」のラベルでエリアを区切っています。

※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。

イベント処理とボタンの接続

「▶ 読み上げる」ボタン(speak())・「停止」ボタン(stop())を command= で接続しています。

※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。

例外処理

try-except で Exception を捕捉しています。あわせて messagebox.showwarning() のダイアログで、ユーザーへの通知・確認を行います。

※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。

6. ステップバイステップガイド

このアプリをゼロから自分で作る手順を解説します。コードをコピーするだけでなく、実際に手順を追って自分で書いてみましょう。

  1. 1
    ファイルを作成する

    新しいファイルを作成して app40.py と保存します。外部ライブラリ pyttsx3 を使います。先に pip install pyttsx3 を実行してください。

  2. 2
    クラスの骨格を作る

    App40 クラスを定義し、__init__ と、末尾の root = tk.Tk()mainloop() の最小構成を書いて、まず空のウィンドウが出ることを確認します。

  3. 3
    ウィンドウを設定する

    title("音声合成アプリ")geometry("500x400")configure(bg="#f8f9fc") をコンストラクタで設定します。

  4. 4
    画面部品を並べる

    _build_ui() の中で、tk.Frame×3・tk.Label×6・ttk.LabelFrame×2・tk.Textttk.Scale×2・tk.Buttonttk.Button を作って配置します(掲載コードと同じ並び順で書くとレイアウトが一致します)。まず表示だけ確認しましょう。

  5. 5
    イベントを接続する

    「2. 機能一覧」で挙げた各ボタンを command= で対応するメソッド(speak()stop())につなぎます。

  6. 6
    中心になるメソッドを実装する

    アプリの本体である speak()(18行)・stop()(4行)・_init_tts()(7行) を実装します。

  7. 7
    動作確認する

    python app40.py で起動し、各ボタンが反応することを確認します。

7. カスタマイズアイデア

基本機能を習得したら、以下のカスタマイズに挑戦してみましょう。少しずつ機能を追加することで、Pythonのスキルが飛躍的に向上します。

💡 ダークモードを追加する

bg色・fg色を辞書で管理し、ボタン1つでダークモード・ライトモードを切り替えられるようにしましょう。

💡 結果をファイルに保存する

このコードに保存処理はないため、表示中の数値や結果はアプリを閉じると消えます。テキストファイルへ書き出して、次回起動時に読み込む機能を追加してみましょう。

💡 入力履歴機能

以前の入力値を覚えておいてComboboxのドロップダウンで再選択できる履歴機能を追加しましょう。

8. よくある問題と解決法

❌ 文字のフォントが崩れる・見た目が違う

原因:このコードは font=("Noto Sans JP", ...) のようにフォント名を直接指定しています。環境にこのフォントが入っていないと、OS が代わりのフォントで表示するため、見本と見た目が変わることがあります。

解決法:動作には支障ありません。気になる場合は font 引数の名前を自分の OS に入っているフォントへ書き換えるか、font 引数を省略してください。

❌ ウィンドウの大きさが画面に合わない

原因:geometry("500x400") の固定サイズで起動するためです。

解決法:この数値を書き換えて起動サイズを調整してください。なお、このコードにはウィンドウサイズを固定する設定(resizable の指定)が無いため、ウィンドウの端をドラッグしたサイズ変更は既定どおり可能です。

9. 練習問題

アプリの理解を深めるための練習問題です。気になるものから挑戦してみてください(課題3は全アプリ共通の定型課題です)。課題を試すには pyttsx3 が動く環境が要ります。読み込めないときは警告ラベルだけの画面になり、設定欄もボタンも出ません。Linux では espeak(espeak-ng)の導入も必要です。

  1. 課題1:音量の数値を速度と同じように表示する

    速度のスライダーの右には数値ラベルがありますが、音量には数値が出ません。音量にも同じ数値表示を足して、いまいくつなのかを見えるようにします。

    期待結果:起動直後は音量スライダーの右に「1.0」と出る。スライダーを左端まで動かすと「0.0」に変わる。

    合格条件

    • 起動直後から数値が見えている(動かすまで空欄にならない)
    • スライダーを動かすたびに表示が追従する
    • 表示は小数第1位まで(0.6031746031746031 のようにならない)

    ヒント①(どこを触るか): 足すのは _build_ui() の音量スライダー(from_=0.0, to=1.0 の行)の直後1か所です。手本は速度側の3行、つまり self.rate_label の作成・grid()trace("w", ...) です。
    ヒント②(使うもの): 音量の行は row=1 で、column=2 が空いています。表示は f"{self.vol_var.get():.1f}" のように書きます。
    つまずきやすい点: rate_vartk.IntVarvol_vartk.DoubleVar です。str() のまま渡すと 0.6031746031746031 のような長い小数が出ます。trace は値が変わったときだけ動くため、ラベルの初期テキストに "1.0" を入れておかないと起動直後が空欄になります。

  2. 課題2:読み上げ中は「▶ 読み上げる」を押せなくする

    いまは読み上げ中にもう一度ボタンを押せて、speak() が新しいスレッドを起動します。読み上げが終わるまではボタンを無効にして、二重に走らないようにしましょう。

    期待結果:「▶ 読み上げる」を押すとボタンが押せなくなり、「✅ 完了」が出るとまた押せるようになる。

    合格条件

    • 読み上げ中は「▶ 読み上げる」が反応しない
    • 「✅ 完了」が出たあとは、もう一度読み上げられる
    • テキスト欄を空にして押したときは警告が出るだけで、ボタンは押せる状態のまま

    ヒント①(どこを触るか): 触るのは3か所です。ボタンの行は tk.Button(...).pack(...) と1文なので、生成と pack() の2文に分けて変数に入れます。無効にするのは speak() のスレッド開始の直前、戻すのは _run()self.root.after(0, ...) の中です。
    ヒント②(使うもの): 無効化は config(state=tk.DISABLED)、戻すのは state=tk.NORMAL です。_run() は別スレッドで動くので、画面の操作は既にある after(0, ...) の中でまとめてください。
    つまずきやすい点: テキストが空のときは警告を出して return する分岐が先にあります。無効化をその手前に書くと、警告のあとボタンが押せないまま残ります。戻す処理は after(0, ...) の1か所だけなので、「停止」でも戻したいなら stop() にも同じ1行を足してください。

  3. 課題3:保存機能の追加

    入力値や計算結果をファイルに保存する機能を追加しましょう。jsonやcsvモジュールを使います。

🚀
次に挑戦するアプリ

このアプリをマスターしたら、次のアプリにも挑戦してみましょう。

🐛
エラーが出て動かないときは

写経中に赤いエラー文が出たら、Pythonエラー一覧&解決法(英語メッセージ逆引き)で原因と直し方をすぐ確認できます。

📖
独学を1冊で体系化するなら

写経しながら「なぜこう書くのか」が気になり始めたら、入門書を1冊通して読むと断片的な知識がつながります。Python入門書のおすすめ2冊(当サイトの参考書ランキング総合1〜2位)で、独学者向けの最初の1冊を比較しています。