Amazon Polly で
問題文を読み上げ
Takanori Suzuki
BPStyle #158 / 2024 Mar 7
背景
某案件で学習教材の電子化をやっている
合理的配慮 の一環としてテキスト読み上げを実現したい
全盲の人向けではなく、聴覚優位 の人向け
合理的配慮
合理的配慮とは、障害者から何らかの助けを求める意思の表明があった場合、過度な負担になり過ぎない範囲で、社会的障壁を取り除くために必要な便宜のことである。
聴覚優位
子どもたちの情報の取り入れ方を下記の3タイプに分類し、“知覚の優位性”という考え方が世界に広がっていったことに始まります。
視覚優先型・聴覚優先型・運動感覚/触覚優先型
やりたいこと
問題文等を読み上げられるようにする
聴覚優位の生徒が理解しやすくする
完全に読み上げられなくてもある程度しょうがない
ちなみに全盲の場合
OSのアクセシビリティ機能を使う
PC上のスクリーンリーダーを使用する
Web側はアクセシビリティに対応する
→今回は対象外
ゴール
Amazon Pollyでの音声合成を知る
Pythonでの実装方法を知る
読み上げのカスタマイズ方法を知る
Amaozon Polly
Amaozon Polly
数十の言語 で高品質で自然な人間の声を展開
12ヶ月間、 毎月 500万文字が無料
クラウド型コールセンターのAmazon Connectでも使える
Amazon Pollyのデモ
多分ytakashimaさんは平成狸合戦ぽんぽこが好き(適当)(nibu.mp3)
Maybe ytakashima-san likes Heisei Raccoon War Ponpoko (appropriate) (nibu-en.mp3)
PythonからAmazon Pollyを実行
(env) $ pip install boto3
(env) $ export AWS_ACCESS_KEY_ID=AKI...
(env) $ export AWS_SECRET_ACCESS_KEY=ZoWb...
(env) $ export AWS_DEFAULT_REGION=ap-northeast-1
Boto3をインストール
APIを使うための環境変数を設定
PythonからAmazon Pollyを実行
from contextlib import closing
from pathlib import Path
import boto3
polly = boto3.client('polly') # クライアント作成
s = "zoom如きにやられるとはニューカマーのツラ汚しよ…" # by masashinji
result = polly.synthesize_speech(
Text=s, OutputFormat="mp3", VoiceId="Mizuki") # テキストから音声を合成
# 音声を読み込んでファイルに保存
with closing(result["AudioStream"]) as stream:
Path("masashinji.mp3").write_bytes(stream.read())
PythonからAmazon Pollyを実行
mp3ファイルができた!! 🎉
s = "zoom如きにやられるとはニューカマーのツラ汚しよ…" # by masashinji
result = polly.synthesize_speech(
Text=s, OutputFormat="mp3", VoiceId="Mizuki") # テキストから音声を合成
Amazon Pollyカスタマイズ
言語の変更
言語と音声は
VoiceId
引数を変更する日本語: Mizuki, Takumi, Kazuha, Tomoko
英語: Ivy, Salli, Joey, Justin…など
result = polly.synthesize_speech(
Text="時すでに🍣…", OutputFormat="mp3", VoiceId="Takumi")
読みの指定
🍣を「お寿司」と読ませたい
<phoneme>
タグでフリガナを指定TextType="ssml"
引数を追加
s = '<speak>時すでに<phoneme type="ruby" ph="おすし">🍣</phoneme>…</speak>'
result = polly.synthesize_speech(Text=s,
TextType="ssml", OutputFormat="mp3", VoiceId="Takumi")
SSMLタグ
音声合成マークアップ言語(SSML) に対応
速度変更(
<prosody>
)一時停止(
<break>
)強調(
<emphasis>
)など
SSMLタグ
s1 = "mypy の目をたしょうごまかしてしまった..."
# 全体をx-slow、mypyに読み設定、「目を」のあとにbreak
s2 = ('<speak><prosody rate="x-slow">'
'<phoneme type="ruby" ph="マイパイ">mypy</phoneme>の目を'
'<break strength="x-strong"/>たしょうごまかしてしまった...'
'</prosody></speak>')
Lexicon で読みをカスタマイズ
Lexicon
発音レキシコン: ファイルで発音をカスタマイズ
<phoneme>
は個別、レキシコンは共通ルール複数ファイルを用意して使い分けも可能
Lexicon
レキシコンの例
<?xml version="1.0" encoding="UTF-8"?>
<lexicon version="1.0"
xmlns="http://www.w3.org/2005/01/pronunciation-lexicon"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.w3.org/2005/01/pronunciation-lexicon
http://www.w3.org/TR/2007/CR-pronunciation-lexicon-20071212/pls.xsd"
alphabet="ipa" xml:lang="ja">
<lexeme>
<grapheme>🍣</grapheme><alias>おすし</alias>
</lexeme>
<lexeme>
<grapheme>ozk</grapheme><alias>よろしく</alias>
</lexeme>
</lexicon>
Lexicon
Amazon Pollyの画面でLexionのデモ
ozkですし🍣...
PythonからLexiconを使用
「yukieは実質天皇()」にLexiconを適用する
<?xml version="1.0" encoding="UTF-8"?>
<lexicon version="1.0"
xmlns="http://www.w3.org/2005/01/pronunciation-lexicon"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.w3.org/2005/01/pronunciation-lexicon
http://www.w3.org/TR/2007/CR-pronunciation-lexicon-20071212/pls.xsd"
alphabet="ipa" xml:lang="ja">
<lexeme>
<grapheme>天皇</grapheme><alias>えんぺらー</alias>
</lexeme>
<lexeme>
<grapheme>()</grapheme><alias>かっこ</alias>
</lexeme>
<lexeme>
<grapheme>①</grapheme><alias>まるいち</alias>
</lexeme>
<lexeme>
<grapheme>②</grapheme><alias>まるに</alias>
</lexeme>
<lexeme>
<grapheme>③</grapheme><alias>まるさん</alias>
</lexeme>
</lexicon>
PythonからLexiconを使用
# Lexiconを名前をつけて登録
LEXICON = "tennolexicon"
data = Path("tenno-lexicon.xml").read_text(encoding="utf-8")
polly.put_lexicon(Name=LEXICON, Content=data)
s = "yukieは実質天皇()"
# Lexiconを使用しない
result = polly.synthesize_speech(Text=s,
OutputFormat="mp3", VoiceId="Mizuki")
# Lexiconを使用
result = polly.synthesize_speech(Text=s,
OutputFormat="mp3", VoiceId="Mizuki",
LexiconNames=[LEXICON])
問題文読み上げでやったこと
Lexiconを作成
①、②:まるいち、まるに
()〔〕:括弧
〜:から
→:やじるし
+:プラス
・:、 (句点と同じ空白が入る)
スペースを <break>
タグに
選択式の文章「〜〜を選べ ① ほげ ② ふが」
スペース部分を 一時停止タグ に置換
<break strength="x-strong"/>
フリガナを <phoneme>
タグに
問題文はHTML形式
フリガナは
<ruby>
タグを使用
<ruby>天皇<rt>えんぺらー</rt></ruby>
↓
<phoneme type="ruby" ph="えんぺらー">天皇</phoneme>
英語と日本語を分割
Mizuki
等日本語音声で英語を読ませると発音がやばい「May the Force be with you.」
英語と日本語を分割
英語と日本語に 分割 し音声読み上げ
1つのmp3にまとめる
→いい感じの音声になりそう
英語と日本語を分割
指定した言語で読み上げる関数
def text2speech(polly, text, f, lang):
"""指定した言語で読み上げ"""
voice = {"en": "Salli", "ja": "Mizuki"}[lang]
result = polly.synthesize_speech(Text=text,
OutputFormat="mp3", VoiceId=voice,
LexiconNames=[LEXICON])
with closing(result["AudioStream"]) as stream:
f.write(stream.read())
英語と日本語を分割
正規表現で日英を分割して読み上げ
text = "()に当てはまる単語を選べ。 "
text += "Do, or do not. () is no try. ① They ② There ③ Then"
with open("yoda.mp3", "wb") as f:
while m := re.search(r"(^|[\s\b])(\w[\w\s/\.,:;'\"!?]*)([\s\b]|$)", text, re.A):
en_text = m.group(2)
ja_text, text = text.split(en_text, maxsplit=1)
text2speech(polly, ja_text, f, "ja")
text2speech(polly, en_text, f, "en")
if text.strip():
text2speech(polly, ja_text, f, "ja")
その他Amazon Polly情報
2種類の音声
標準音声とニューラル音声がある
ニューラル音声の方がよいいい感じ
対応している声が異なる
ニューラル音声の方がお高い
2種類の音声
ピックアップした問題文を2種類の音声で生成
聞き比べるHTMLを生成してブラウザで確認
同期処理の 文字数制限
synthesize_speech()
での音声合成は文字数制限がある長文は
start_speech_synthesis_task()
で非同期処理結果はS3に保存される
get_speech_synthesis_task()
でタスクの状態が取れる
数式読み上げ
問題文ではmathjaxで数式を描画
svgになっているため読み上げできない
ライブラリを最新にあげるとMathMLも出力されるっぽい
数式も読み上げられるようになるかも!?
まとめ
Amazon Pollyで音声合成は簡単にできる
多言語に対応
細かい調整も可能
なにかに使えるかも?
Thank You 🙏
@takanory takanory takanory takanory