Closed4

「pyopenjtalk」を試す

kun432kun432

GitHubレポジトリ

https://github.com/r9y9/pyopenjtalk

pyopenjtalk

OpenJTalk の python ラッパー

このパッケージは二つのコアコンポーネントで構成されています:

  • OpenJTalk ベースのテキスト処理フロントエンド
  • HTSEngine を用いた音声合成バックエンド

注意

  • このパッケージは OpenJTalk の修正版でビルドされています。修正されたバージョンは、いくつかの改良 (例えば cmake のサポート) を加えつつ、同じ機能を提供しますが、技術的には HTS ワーキンググループのものとは異なります。
  • また、このパッケージは修正版の hts_engine_API を使用しています。上記と同様です。

pyopenjtalkパッケージを使用する前に、2つのソフトウェアのLICENSEを見てください。

ビルド要件

pythonパッケージはopen_jtalkとhts_engine_APIのpythonバインディングを作るためにcythonに依存しています。pyopenjtalkのビルドとインストールには以下のツールが必要です:

  • C/C++ コンパイラ (C/C++ エクステンションをビルドするため)
  • cmake
  • cython

対応プラットフォーム

  • Linux
  • Mac OSX
  • Windows (MSVC) (この PR を参照)

OpenJTalkはすごい昔に触った記憶があるのだけど、今回はTTSとしてではなく、音素変換やアクセント情報の取得ができるというのを聞いたので試してみたいと思う。

参考)

https://note.com/npaka/n/n6a5307cf8fe1

https://www.negi.moe/negitalk/openjtalk.html

kun432kun432

Colaboratoryで。

インストール。ソースからビルドされるので多少時間がかかる。

!pip install pyopenjtalk
!pip freeze | grep -i pyopenjtalk
出力
pyopenjtalk==0.3.4

まずは普通にTTS。

import pyopenjtalk
from scipy.io import wavfile
import numpy as np

x, sr = pyopenjtalk.tts("おめでとうございます")
wavfile.write("test.wav", sr, x.astype(np.int16))

再生

from IPython.display import Audio, display

display(Audio("test.wav", autoplay=True))

こういう感じ

https://audio.com/kun432/audio/pyopenjtalk-jp-sample-1

で本題。

テキスト処理フロントエンドのみの実行。フルコンテキストラベルという、音素を含む言語特徴量と音素アライメントが格納された情報が取得できる。

import pyopenjtalk

pyopenjtalk.extract_fullcontext("こんにちは")
出力
['xx^xx-sil+k=o/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:5_5%0_xx_xx/H:xx_xx/I:xx-xx@xx+xx&xx-xx|xx+xx/J:1_5/K:1+1-5',
 'xx^sil-k+o=N/A:-4+1+5/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'sil^k-o+N=n/A:-4+1+5/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'k^o-N+n=i/A:-3+2+4/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'o^N-n+i=ch/A:-2+3+3/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'N^n-i+ch=i/A:-2+3+3/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'n^i-ch+i=w/A:-1+4+2/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'i^ch-i+w=a/A:-1+4+2/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'ch^i-w+a=sil/A:0+5+1/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'i^w-a+sil=xx/A:0+5+1/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'w^a-sil+xx=xx/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:5_5!0_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:xx_xx%xx_xx_xx/H:1_5/I:xx-xx@xx+xx&xx-xx|xx+xx/J:xx_xx/K:1+1-5']

このフォーマットについて説明されたPDFがあるようなのだが、リンク切れ。。。。

以下に簡単にまとめておられる方がいた。

https://gist.github.com/yamachu/5dab4393ade50173bebe51c65cd9e8f3

/A:
アクセント句の場所とその相対位置
0+1+4 => アクセントからの経過位置(後にアクセントがある場合は負の値をとる)
+ アクセント句内のモーラ位置(1スタート)
+ アクセント句のモーラの数からモーラ位置を引いて1を足した数(非負)

たしかにアクセント情報が含まれている様子。

次に音素変換(g2p)

import pyopenjtalk

pyopenjtalk.g2p("こんにちは")
出力
k o N n i ch i w a

カタカナで出力させることもできる

pyopenjtalk.g2p("こんにちは", kana=True)
出力
コンニチワ

run_marineオプションを有効にしてインストールすると、marineを使ったディープニューラルネットワークによる、より正確なアクセント推定ができるらしい。

パッケージをインストールし直し。ランタイムの再起動が必要になる。

!pip install pyopenjtalk[marine]

TTSで有効化

import pyopenjtalk
from scipy.io import wavfile
import numpy as np
from IPython.display import Audio, display

x, sr = pyopenjtalk.tts("おめでとうございます", run_marine=True)  # `run_marine`を有効化
wavfile.write("test.wav", sr, x.astype(np.int16))

display(Audio("test.wav", autoplay=True))

https://audio.com/kun432/audio/pyopenjtalk-jp-sample-2

フルコンテキストラベルを取得

pyopenjtalk.extract_fullcontext("こんにちは", run_marine=True) # `run_marine`を有効化
出力
['xx^xx-sil+k=o/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:5_5%0_xx_xx/H:xx_xx/I:xx-xx@xx+xx&xx-xx|xx+xx/J:1_5/K:1+1-5',
 'xx^sil-k+o=N/A:-4+1+5/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'sil^k-o+N=n/A:-4+1+5/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'k^o-N+n=i/A:-3+2+4/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'o^N-n+i=ch/A:-2+3+3/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'N^n-i+ch=i/A:-2+3+3/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'n^i-ch+i=w/A:-1+4+2/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'i^ch-i+w=a/A:-1+4+2/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'ch^i-w+a=sil/A:0+5+1/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'i^w-a+sil=xx/A:0+5+1/B:xx-xx_xx/C:09_xx+xx/D:xx+xx_xx/E:xx_xx!xx_xx-xx/F:5_5#0_xx@1_1|1_5/G:xx_xx%xx_xx_xx/H:xx_xx/I:1-5@1+1&1-1|1+5/J:xx_xx/K:1+1-5',
 'w^a-sil+xx=xx/A:xx+xx+xx/B:xx-xx_xx/C:xx_xx+xx/D:xx+xx_xx/E:5_5!0_xx-xx/F:xx_xx#xx_xx@xx_xx|xx_xx/G:xx_xx%xx_xx_xx/H:1_5/I:xx-xx@xx+xx&xx-xx|xx+xx/J:xx_xx/K:1+1-5']

その他、ドキュメントにはカスタム辞書の作成などについても書かれている。

kun432kun432

アクセントの箇所の意味がイマイチ理解できないので、ChatGPTと会話しながら調べてみた。

なので間違ってるかもしれない、あまり精査もしてない

アクセント情報は、「アクセント核」=高音になる位置の情報を含んでいる。

A: <アクセント核までの距離> + <次のモーラ数> + <文全体のモーラ数>

例えば、「こんにちは」の「こ」のアクセント情報。

A:-4+1+5
  • +5: 「こんにちは」は5モーラ(「こ・ん・に・ち・は」)で構成される。
  • -4: 現在の音素(「こ」)はアクセント核から4モーラ「前」(負の場合は前)にある。つまり「は」。
  • +1: アクセント核の直後に続くモーラの数。「は」のあとにはなにもないのだが実際には終端の無音(sil)が含まれるので+1になるらしい。

「こんにちは」のそれぞれのアクセント情報はこうなる。

モーラ Aフィールド アクセント核の状況
A:-4+1+5 アクセント核まで4モーラ前
A:-4+1+5 撥音のため、直前の「こ」と同じ扱い
A:-3+2+4 アクセント核まで3モーラ前
A:-2+3+3 アクセント核まで2モーラ前
A:0+5+1 アクセント核そのもの(高音部分)。最後にsilがついている。

「ん」「っ」「ー」はモーラ的には1でカウントするのだけど、フルコンテキストラベルにおけるアクセント情報の計算では音をベースに考えるらしい。

なお、実際にはフルコンテキストラベルの出力は音素ごとに出力されている

音素 Aフィールド
k A:-4+1+5
o A:-4+1+5
N A:-4+1+5
n A:-3+2+4
i A:-3+2+4
ch A:-2+3+3
i A:-2+3+3
h A:0+5+1
a A:0+5+1
kun432kun432

音声学をもっと学ばねば、と感じる。

このスクラップは10日前にクローズされました