Amazon Polly
問題文を読み上げ

Takanori Suzuki

Python Kansai #03 / 2024 Apr 17

アジェンダ 📜

  • 背景とゴール

  • Amazon Pollyの基本

  • 読み上げをカスタマイズ

  • 問題文読み上げでやったこと

背景 🏞️

  • 学習教材の 電子化 プロジェクト

  • 合理的配慮 の一環としてのテキスト読み上げ

  • 全盲の人向けではなく、聴覚優位 の人向け

合理的配慮

合理的配慮とは、障害者から何らかの助けを求める意思の表明があった場合、過度な負担になり過ぎない範囲で、社会的障壁を取り除くために必要な便宜のことである。

聴覚優位

子どもたちの情報の取り入れ方を下記の3タイプに分類し、“知覚の優位性”という考え方が世界に広がっていったことに始まります。

視覚優先型・聴覚優先型・運動感覚/触覚優先型

やりたいこと

  • 問題文などを 読み上げ られる

    • 聴覚優位 の生徒が理解しやすく

  • 完全な読み上げじゃなくてもよい

ちなみに全盲の場合

ゴール 🥅

  • Amazon Polly での音声合成を知る

  • Pythonでの 実装方法 を知る

  • 読み上げの カスタマイズ方法 を知る

Photos 📷 Tweets 🐦 👍

#pythonkansai / #mixleap

@takanory

Slides / スライド 💻

slides.takanory.net

slides.takanory.net

Who am I? / お前 誰よ 👤

takanory profile kuro-chan and kuri-chan

PyCon JP Association 🐍

日本国内のPythonユーザのために、Pythonの普及及び開発支援を行うために、継続的にカンファレンス(PyCon)を開くことを目的とした 非営利組織

www.pycon.jp

pycon jp logo

PyCon JP 2024 🇯🇵

BeProud Inc. 🏢

  • BeProud: Pythonシステム開発、コンサル

  • connpass: IT勉強会支援プラットフォーム

  • PyQ: Python独学プラットフォーム

  • TRACERY: システム開発ドキュメントサービス

BeProud logos

PyPro4 📕

PyPro4

Amazon Pollyの基本 🗣️

Amaozon Polly

Amazon Pollyの画面

Amazon Pollyの画面

Amazon Pollyの画面

  • Amazon Pollyの画面からmp3をダウンロード

  • 「私の戦闘力は530000です」(power-ja.mp3)

  • 「My power level is 530,000」(power-en.mp3)

PythonからAmazon Pollyを実行

  • Boto3をインストール

  • APIを使うために環境変数を設定

$ python3.12 -m venv env
$ . env/bin/activate
(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

PythonからAmazon Pollyを実行

from contextlib import closing
from pathlib import Path
import boto3

polly = boto3.client('polly')  # クライアント作成
s = "きえろぶっとばされんうちにな"

# テキストから音声を合成
result = polly.synthesize_speech(
    Text=s, OutputFormat="mp3", VoiceId="Mizuki")

# 音声を読み込んでファイルに保存
with closing(result["AudioStream"]) as stream:
    Path("yamcha-kiero.mp3").write_bytes(stream.read())

PythonからAmazon Pollyを実行

s = "きえろぶっとばされんうちにな"

# テキストから音声を合成
result = polly.synthesize_speech(
    Text=s, OutputFormat="mp3", VoiceId="Mizuki")

Amazon Pollyの基本まとめ 🗣️

  • AWSの画面 から使える

  • Pythonでは Boto3 経由で使える

読み上げをカスタマイズ 🔧

言語の変更

  • VoiceId引数で 言語と音声 を指定

  • 参考: Amazon Polly の音声

    • 日本語: Mizuki, Takumi, Kazuha, Tomoko

    • 英語: Ivy, Salli, Joey, Justinなど

result = polly.synthesize_speech(
    Text="超サイヤ人孫悟空だ!!!!!",
    OutputFormat="mp3", VoiceId="Takumi")

読みの指定

  • 超サイヤ人の「超」を スーパー と読ませたい

  • <phoneme> タグでフリガナを指定

  • 全体を <speak> タグで囲む

s = ('<speak><phoneme type="ruby" ph="すーぱー">超</phoneme>'
     'サイヤ人孫悟空だ!!!!!</speak>')

読みの指定

  • TextType="ssml" 引数を追加

result = polly.synthesize_speech(
    Text=s, TextType="ssml", OutputFormat="mp3", VoiceId="Takumi")

SSMLタグ

SSMLタグ

s1 = "クリリンのことか……クリリンのことかーーーっ!!!!!"
# 間を開ける、呼吸音追加、強調する
s2 = ('<speak><p>クリリンのことか……</p>'
      '<amazon:breath duration="medium" volume="x-loud"/>'
      '<p><emphasis level="strong">クリリンのことかーーーっ!!!!!'
      '</emphasis></p></speak>')

読み上げをカスタマイズまとめ 🔧

  • 言語音声 を変更できる

  • 読み を指定できる

  • SSMLタグ でカスタマイズできる

Lexicon で読みをカスタマイズ 🛠️

Lexicon とは

  • 発音レキシコン: 発音の定義ファイル

  • <phoneme>個別、レキシコンは 共通

  • 複数ファイルを用意して使い分けも可能

  • 参考: レキシコンの管理

Lexiconを使用する(画面)

  • Lexiconファイルを作成

db-lexicon.xml
<?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>
  <lexeme>
    <grapheme>③</grapheme><alias>まるさん</alias>
  </lexeme>
  <lexeme>
    <grapheme>④</grapheme><alias>まるよん</alias>
  </lexeme>
</lexicon>

Lexiconを使用する(画面)

  • 名前を付けてXMLファイルをアップロード

Lexiconをアップロード

Lexiconを使用する(画面)

  • レキシコンを指定する

Lexiconを使用

Lexiconを使用する(画面)

PythonからLexiconを使用

# Lexiconを名前をつけて登録
LEXICON = "DBLexicon"
data = Path("db-lexicon.xml").read_text(encoding="utf-8")
polly.put_lexicon(Name=LEXICON, Content=data)

s = "魔貫光殺砲うけてみろーーーーーーっ!!!!"
# Lexiconを使用しない
result = polly.synthesize_speech(
    Text=s, OutputFormat="mp3", VoiceId="Takumi")

# Lexiconを使用
result = polly.synthesize_speech(
    Text=s, OutputFormat="mp3", VoiceId="Takumi",
    LexiconNames=[LEXICON])

Lexiconで読みをカスタマイズまとめ 🛠️

  • Lexiconファイル をXMLで作成

  • Lexiconファイルを 登録

  • 任意のLexiconを 適用

問題文読み上げでやったこと 📖

Lexicon を作成

  • ①、②:まるいち、まるに

  • ()〔〕:括弧

  • 〜:から

  • →:やじるし

  • +:プラス

  • ・:、 (句点と同じ空白が入る)

スペースを <break> タグに

  • スペース部分を 一時停止タグ に置換

  • 選択肢のラベルを区切って読む

〜〜を選べ ① ほげ ② ふが

〜〜を選べ<break strength="x-strong"/>①<break strength="x-strong"/>ほげ<break strength="x-strong"/>②<break strength="x-strong"/>ふが

フリガナを <phoneme> タグに

  • 問題文はHTML形式

  • フリガナはHTMLの <ruby> タグ

<ruby>魔貫光殺砲<rt>まかんこうさっぽう</rt></ruby>

<phoneme type="ruby" ph="まかんこうさっぽう">魔貫光殺砲</phoneme>

日本語の英語の混ざった文章

日本語の英語の混ざった文章

  • 英語の問題文は日本語と英語が 混ざっている

  • 日本語と英語に 分割 し音声読み上げ

  • 1つのmp3にまとめる

日本語の英語の混ざった文章

  • 日本語と英語の混ざった文の例

  • vegeta-english.txt

次の日本語を英訳したときの()に当てはまる単語を選べ。
「がんばれカカロット お前がナンバーワンだ!」
Hang in there Kakarot, you are () one!
① only ② number ③ minus ④ another

日本語の英語の混ざった文章

  • ()、①を読ませるために Lexicon を登録

  <lexeme>
    <grapheme>()</grapheme><alias>カッコ</alias>
  </lexeme>
  <lexeme>
    <grapheme>①</grapheme><alias>まるいち</alias>
  </lexeme>
  <lexeme>
    <grapheme>②</grapheme><alias>まるに</alias>
  </lexeme>
# Lexiconを名前をつけて登録
LEXICON = "DBLexicon"
data = Path("db-lexicon.xml").read_text(encoding="utf-8")
polly.put_lexicon(Name=LEXICON, Content=data)

日本語の英語の混ざった文章

  • 指定した言語で読み上げる 関数


def text2speech(polly, text, f, lang):
    """指定した言語で読み上げる"""
    # 言語で音声切り替え
    voice = {"en": "Matthew", "ja": "Takumi"}[lang]
    result = polly.synthesize_speech(Text=text,
        OutputFormat="mp3", VoiceId=voice,
        LexiconNames=[LEXICON])
    with closing(result["AudioStream"]) as stream:
        f.write(stream.read())

日本語の英語の混ざった文章

  • 正規表現で 日英を分割 して読み上げ

# ファイルから問題文読み込み
p = Path("vegeta-english.txt")
text = p.read_text(encoding="utf-8")

with open("vegeta-question.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")

日本語の英語の混ざった文章

次の日本語を英訳したときの()に当てはまる単語を選べ。
「がんばれカカロット お前がナンバーワンだ!」
Hang in there Kakarot, you are () one!
① only ② number ③ minus ④ another

問題文読み上げでやったことまとめ 📖

  • Lexiconを作成

  • スペースを <break> タグに

  • フリガナを <phoneme> タグに

  • 日英の混ざった文章対応

Amazon Pollyの補足情報 🔍

2種類の音声

  • 標準音声ニューラル音声 がある

  • ニューラル音声の方がよりいい感じ

  • 対応している声が異なる

  • ニューラル音声の方がお高い

  • 参考: Amazon Polly の音声

2種類の音声

  • 複数の問題文を 2種類の音声 で生成

  • 聞き比べ用HTML を作成しブラウザで確認

同期処理の 文字数制限

  • synthesize_speech()文字数制限 あり

  • start_speech_synthesis_task() で長文を 非同期 処理

  • get_speech_synthesis_task()状態 を取得

数式読み上げ

  • 問題文では mathjaxで数式 を描画

  • svgになっているため読み上げできない

  • mathjaxが出力するMathMLを読み上げるといけるかも?→現在調査中

まとめ 📚

  • Amazon Pollyで音声合成は 簡単 にできる

  • 多言語 に対応

  • SSMLLexicon で細かい調整が可能

  • ぜひ試してみてください

  • サンプルコード: code

Thank You 🙏

slides.takanory.net

@takanory takanory takanory takanory

takanory profile kuro-chan and kuri-chan