piper-plus-g2p: TTS不要で使えるMITライセンスの多言語G2Pパッケージ

初めに

今回は、piper-plusから独立したG2P(Grapheme-to-Phoneme、文字から音素への変換)パッケージ「piper-plus-g2p」を紹介します。

piper-plus-g2pは、TTSエンジンを使わずに8言語(ja, en, zh, ko, es, fr, pt, sv)のテキストをIPA音素列に変換できるパッケージです。このうちTTSモデルが公開済みなのは6言語(ja, en, zh, es, fr, pt)ですが、G2P単体としては8言語すべてに対応しています。従来のpiper-plusではeSpeak-ng(GPLライセンス)に依存していましたが、piper-plus-g2pはeSpeak-ngを完全に排除し、MITライセンスで利用できます。PR #300(2026-04-01)で追加し、2026-04-07にPyPI(v0.2.0)およびcrates.io(v0.2.0)で公開しました。

設計方針として「IPA-first」を採用しており、phonemize() はBOS/EOS/PUAマーカーを含まない純粋なIPAトークン列を返します。Piper互換のphoneme_idsが必要な場合はオプションの PiperEncoder を使います。

Python(PyPI: v0.2.0)、npm(@piper-plus/g2p: v0.2.0)、Rust(crates.io: v0.2.0)、Goの4つのSDKを提供しており、4実装間でPUA互換テーブル(96エントリ)をCIで一致検証しています。

github.com

開発環境

  • OS: Windows 11
  • Python: 3.12
  • パッケージマネージャー: uv
  • piper-plus-g2p: 0.2.0

環境構築

全言語をインストールする場合

uv init piper-g2p-demo
cd piper-g2p-demo
uv add "piper-plus-g2p[all]>=0.2.0"

日本語のみの場合

uv add "piper-plus-g2p[ja]"

個別言語のextras

必要な言語だけをインストールすることで依存関係を最小限にできます。

extra 言語 主な依存 備考
[ja] 日本語 pyopenjtalk-plus
[en] 英語 CMU Dictionary + g2p-en
[zh] 中国語 ピンイン辞書
[ko] 韓国語 g2pk2 (MeCab/eunjeon) Windows環境ではMeCabのインストールに問題が発生する場合あり
[es] スペイン語 ルールベース
[fr] フランス語 ルールベース
[pt] ポルトガル語 ルールベース
[sv] スウェーデン語 NST辞書

複数言語を組み合わせる場合は以下のように指定します。

uv add "piper-plus-g2p[ja,en,zh]"

基本的な使い方

言語ごとのPhonemizer取得

get_phonemizer() に言語コードを渡すと、対応するPhonemizerインスタンスが返されます。

from piper_plus_g2p import get_phonemizer

# 日本語のPhonemizer
ja_phonemizer = get_phonemizer("ja")

# テキストをIPA音素列に変換
result = ja_phonemizer.phonemize("こんにちは")
print(result)
['k', 'o', '[', 'N_n', 'n', 'i', 'ch', 'i', 'w', 'a']

各言語の出力例

from piper_plus_g2p import get_phonemizer

# 日本語
ja = get_phonemizer("ja")
print("ja:", ja.phonemize("こんにちは"))

# 英語
en = get_phonemizer("en")
print("en:", en.phonemize("Hello, world!"))

# 中国語
zh = get_phonemizer("zh")
print("zh:", zh.phonemize("你好世界"))
ja: ['k', 'o', '[', 'N_n', 'n', 'i', 'ch', 'i', 'w', 'a']
en: ['h', 'ə', 'l', 'ˈ', 'o', 'ʊ', ',', ' ', 'w', 'ˈ', 'ɜ', 'ː', 'l', 'd', '!']
zh: ['n', 'i', 'tone2', 'x', 'aʊ', 'tone3', 'ʂ', 'ɻ̩', 'tone4', 'tɕ', 'iɛ', 'tone4']

注意: 韓国語(ko)のG2Pはg2pk2を使用しており、内部でMeCab(eunjeon)を必要とします。Windows環境ではMeCabのインストールに問題が発生する場合があるため、上記の例からは除外しています。Linux/macOS環境であれば get_phonemizer("ko") で利用可能です。

IPA-first設計のため、出力にはBOS(^)やEOS($)などのマーカーは含まれません。

日本語の韻律情報(ProsodyInfo)

日本語Phonemizerでは、音素列に加えて韻律情報(アクセント・イントネーション)も取得できます。

from piper_plus_g2p import get_phonemizer

ja = get_phonemizer("ja")
tokens, prosody = ja.phonemize_with_prosody("こんにちは")
print("prosody:", prosody[0])
prosody: ProsodyInfo(a1=-4, a2=1, a3=5)

OpenJTalkのアクセント情報(a1, a2, a3)がそのまま取得でき、TTS学習時のアクセント制御に利用できます。

PiperEncoderでphoneme_idsに変換(オプション)

Piper互換のphoneme_idsが必要な場合は PiperEncoder を使います。

from piper_plus_g2p import get_phonemizer, PiperEncoder
import json

ja = get_phonemizer("ja")
phonemes = ja.phonemize("こんにちは")

# config.jsonからphoneme_id_mapを読み込み
with open("config.json") as f:
    config = json.load(f)

encoder = PiperEncoder(config["phoneme_id_map"])
phoneme_ids = encoder.encode(phonemes)
print(phoneme_ids)

PiperEncoder はコンストラクタに phoneme_id_map(モデルの config.json に含まれる音素→IDのマッピング辞書)が必要です。BOS(ID: 1)とEOS(ID: 2)は自動付加されます。

多言語テキストの自動言語検出

MultilingualPhonemizer

日本語と英語が混在するテキストを処理する場合は、get_phonemizer() にハイフン区切りの複合言語コード(例: "ja-en-zh")を渡します。内部で MultilingualPhonemizer が自動的に作成され、Unicodeブロックに基づく言語検出で混在テキストを処理します。

from piper_plus_g2p import get_phonemizer

# ハイフン区切りの複合言語コードを渡すとMultilingualPhonemizerが自動生成される
phonemizer = get_phonemizer("ja-en")

# 日本語・英語混在テキスト
result = phonemizer.phonemize("Dockerコンテナを起動する")
print(result)

言語検出はUnicodeブロックに基づいて自動判定されます。

文字種 判定言語
カナ文字を含む 日本語 (ja)
CJK文字(カナなし) 中国語 (zh)
ハングル 韓国語 (ko)
ラテン文字 英語 (en)

カスタム辞書サポート

特定の単語の読みをカスタマイズする場合、CustomDictionary クラスを使います。apply_to_text() でテキスト内の単語を辞書の読みに置換し、その後にG2Pで音素変換する流れです。

from piper_plus_g2p import get_phonemizer
from piper_plus_g2p.custom_dict import CustomDictionary

# カスタム辞書を作成し、単語を追加
d = CustomDictionary()
d.add_word("Docker", "ドッカー", priority=10)
d.add_word("piper", "パイパー", priority=10)

# テキストに辞書を適用してから音素変換
text = "Dockerコンテナを起動する"
text = d.apply_to_text(text)  # "ドッカーコンテナを起動する"

ja = get_phonemizer("ja")
result = ja.phonemize(text)
print(result)

JSON辞書ファイルからの読み込みにも対応しています。

d = CustomDictionary(dict_paths="my_dict.json")

他のSDKについて

piper-plus-g2pはPython以外にも複数のSDKを提供しています。

npm (@piper-plus/g2p: v0.2.0)

npm install @piper-plus/g2p
import { G2P, DictLoader } from "@piper-plus/g2p";

// 日本語にはOpenJTalk辞書の読み込みが必要
const loader = new DictLoader();
const jaDict = await loader.loadJaDict();
const g2p = await G2P.create({ languages: ["ja", "en"], jaDict });

const result = g2p.phonemize("こんにちは");
console.log(result.tokens);   // ['k', 'o', ...]
console.log(result.language);  // 'ja'

g2p.dispose();

G2P.create() は非同期ファクトリで、日本語はOpenJTalk WASM + 辞書データの初期化が必要です。DictLoader が辞書のダウンロードとIndexedDBキャッシュを管理します。phonemize(){ tokens, prosody, language } を返します。韻律情報が必要な場合は phonemizeWithProsody() を使います。380テストでカバーしています。

Rust (crates.io: v0.2.0)

cargo add piper-plus-g2p@0.2.0 --features naist-jdic
use piper_plus_g2p::Phonemizer;
use piper_plus_g2p::japanese::JapanesePhonemizer;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let ja = JapanesePhonemizer::new_bundled()?;
    let (tokens, prosody) = ja.phonemize_with_prosody("こんにちは")?;
    println!("{:?}", tokens);
    Ok(())
}

PhonemizerRegistry を使って複数言語をまとめて管理することもできます。Feature flagsで言語ごとの依存を制御可能です(features = ["naist-jdic", "english"])。390テストでカバーしています。

Go

Go SDKのG2Pはモノレポ内のモジュールとして提供しています。

import "github.com/ayutaz/piper-plus/src/go/phonemize"

// 各言語のPhonemizerを個別に作成
esPhonemizer := phonemize.NewSpanishPhonemizer()
result, err := esPhonemizer.PhonemizeWithProsody("Hola, mundo")

日本語G2PはCGO + OpenJTalkが必要です。詳細はGo APIサーバーの記事を参照してください。

4実装間の互換性保証

Python、npm、Rust、Goの4実装は、PUA互換テーブル(96エントリ)をCIで一致検証しています。どのSDKを使っても同じテキストに対して同じ音素列が生成されます。

所感

piper-plus-g2pはTTSを使わずに音素変換だけを利用したいユースケース(発音辞書生成、語学アプリ、音声検索のインデックス構築など)に適しています。eSpeak-ng(GPL)を排除してMITライセンスで8言語のG2Pが使える点は、商用プロダクトへの組み込みにも有利です。4つのSDKでCIレベルの互換性が保証されている点も実用的です。