diff --git a/README.rst b/README.rst index e0094d7..65170c9 100644 --- a/README.rst +++ b/README.rst @@ -50,7 +50,7 @@ * Talk * ja * `Amazon Pollyで問題文読み上げ、数式も読み上げ `__ - * + * `Slides `__ - * BPStyle #163 * Online * 2024 Aug diff --git a/slides/20240831pyconshizu/audio/friend-en.mp3 b/slides/20240831pyconshizu/audio/friend-en.mp3 new file mode 100644 index 0000000..52f70ec Binary files /dev/null and b/slides/20240831pyconshizu/audio/friend-en.mp3 differ diff --git a/slides/20240831pyconshizu/audio/friend-ja.mp3 b/slides/20240831pyconshizu/audio/friend-ja.mp3 new file mode 100644 index 0000000..0091dcd Binary files /dev/null and b/slides/20240831pyconshizu/audio/friend-ja.mp3 differ diff --git a/slides/20240831pyconshizu/audio/hando-no-lexicon.mp3 b/slides/20240831pyconshizu/audio/hando-no-lexicon.mp3 new file mode 100644 index 0000000..aa6145c Binary files /dev/null and b/slides/20240831pyconshizu/audio/hando-no-lexicon.mp3 differ diff --git a/slides/20240831pyconshizu/audio/hando-with-lexicon.mp3 b/slides/20240831pyconshizu/audio/hando-with-lexicon.mp3 new file mode 100644 index 0000000..c170132 Binary files /dev/null and b/slides/20240831pyconshizu/audio/hando-with-lexicon.mp3 differ diff --git a/slides/20240831pyconshizu/audio/mathml.mp3 b/slides/20240831pyconshizu/audio/mathml.mp3 new file mode 100644 index 0000000..9167fb8 Binary files /dev/null and b/slides/20240831pyconshizu/audio/mathml.mp3 differ diff --git a/slides/20240831pyconshizu/audio/not-afraid.mp3 b/slides/20240831pyconshizu/audio/not-afraid.mp3 new file mode 100644 index 0000000..f28ce82 Binary files /dev/null and b/slides/20240831pyconshizu/audio/not-afraid.mp3 differ diff --git a/slides/20240831pyconshizu/audio/question.mp3 b/slides/20240831pyconshizu/audio/question.mp3 new file mode 100644 index 0000000..11a099c Binary files /dev/null and b/slides/20240831pyconshizu/audio/question.mp3 differ diff --git a/slides/20240831pyconshizu/audio/sggk.mp3 b/slides/20240831pyconshizu/audio/sggk.mp3 new file mode 100644 index 0000000..233d99e Binary files /dev/null and b/slides/20240831pyconshizu/audio/sggk.mp3 differ diff --git a/slides/20240831pyconshizu/audio/super-great.mp3 b/slides/20240831pyconshizu/audio/super-great.mp3 new file mode 100644 index 0000000..0c6c7ac Binary files /dev/null and b/slides/20240831pyconshizu/audio/super-great.mp3 differ diff --git a/slides/20240831pyconshizu/code/mathml-formula.html b/slides/20240831pyconshizu/code/mathml-formula.html new file mode 100644 index 0000000..a3836ae --- /dev/null +++ b/slides/20240831pyconshizu/code/mathml-formula.html @@ -0,0 +1,3 @@ +
二次方程式 ax2+bx+c=0 の解は + +x=b±b24ac2a
diff --git a/slides/20240831pyconshizu/code/mathml-sample.html b/slides/20240831pyconshizu/code/mathml-sample.html new file mode 100644 index 0000000..ace9fd0 --- /dev/null +++ b/slides/20240831pyconshizu/code/mathml-sample.html @@ -0,0 +1,13 @@ + + + + x= + + + b±b24ac + 2a + + + + + diff --git a/slides/20240831pyconshizu/code/polly-mathml.py b/slides/20240831pyconshizu/code/polly-mathml.py new file mode 100644 index 0000000..ac045a1 --- /dev/null +++ b/slides/20240831pyconshizu/code/polly-mathml.py @@ -0,0 +1,69 @@ +from contextlib import closing +from pathlib import Path + +import boto3 +from bs4 import BeautifulSoup + +# 識別子、演算子と読みの対応表 +OPERATORS = { + "±": "プラスマイナス", + "+": "プラス", + "−": "マイナス", + "×": "掛ける", + "÷": "割る", + "(": "括弧", + ")": "括弧閉じ", +} + + +def mathml_to_text(text: str) -> str: + """MathMLを取りだして読み上げ文字列にする""" + soup = BeautifulSoup(text, "html.parser") + for math in soup.find_all("math"): + # ここで変換処理 + + # 識別子、演算子を変換する + for mo_mi in math.find_all(["mo", "mi"]): + if mo_mi.text in OPERATORS: + mo_mi.string = OPERATORS[mo_mi.text] + + # 分母と分子の順番を逆にする + for mfrac in math.find_all("mfrac"): + bunshi, bunbo = mfrac.contents + mfrac.clear() + mfrac.append(bunbo) + mfrac.append("ぶんの") + mfrac.append(bunshi) + + # x2 を「x2乗」と読む + for msup in math.find_all("msup"): + exponent = msup.contents[1] + exponent.string = f"{exponent.text}乗" + + # を「ルート」と読む + for msqrt in math.find_all("msqrt"): + msqrt.insert(0, "ルート") + + # その他の記号を変換する + math_text = math.text + math_text = math_text.replace("//", "平行") + math_text = math_text.replace("∘C", "℃") + math_text = math_text.replace("∘", "度") + math.string = math_text + + # 最後に全部テキストにする + return soup.text + + +if __name__ == "__main__": + p = Path("mathml-formula.html") + text = p.read_text(encoding="utf-8") + result = mathml_to_text(text) + print(result) + + polly = boto3.client('polly') + result = polly.synthesize_speech( + Text=result, OutputFormat="mp3", VoiceId="Mizuki") + + with closing(result["AudioStream"]) as stream: + Path("mathml.mp3").write_bytes(stream.read()) diff --git a/slides/20240831pyconshizu/code/polly.py b/slides/20240831pyconshizu/code/polly.py new file mode 100644 index 0000000..2cd2f6d --- /dev/null +++ b/slides/20240831pyconshizu/code/polly.py @@ -0,0 +1,14 @@ +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("not-afraid.mp3").write_bytes(stream.read()) diff --git a/slides/20240831pyconshizu/code/polly2.py b/slides/20240831pyconshizu/code/polly2.py new file mode 100644 index 0000000..285982f --- /dev/null +++ b/slides/20240831pyconshizu/code/polly2.py @@ -0,0 +1,12 @@ +from contextlib import closing +from pathlib import Path +import boto3 + +polly = boto3.client('polly') + +result = polly.synthesize_speech( + Text="S・G・G・K 若林源三", + OutputFormat="mp3", VoiceId="Takumi") + +with closing(result["AudioStream"]) as stream: + Path("sggk.mp3").write_bytes(stream.read()) diff --git a/slides/20240831pyconshizu/code/polly3.py b/slides/20240831pyconshizu/code/polly3.py new file mode 100644 index 0000000..8e86687 --- /dev/null +++ b/slides/20240831pyconshizu/code/polly3.py @@ -0,0 +1,18 @@ +from contextlib import closing +from pathlib import Path +import boto3 + +polly = boto3.client('polly') + +# speakタグとphonemeタグ +s = ('S・G・' + 'G・K・' + '若林源三。') + +# 引数に TextType="ssml" を追加 +result = polly.synthesize_speech( + Text=s, TextType="ssml", OutputFormat="mp3", VoiceId="Takumi") + +# 音声を読み込んでファイルに保存 +with closing(result["AudioStream"]) as stream: + Path("super-great.mp3").write_bytes(stream.read()) diff --git a/slides/20240831pyconshizu/code/polly4.py b/slides/20240831pyconshizu/code/polly4.py new file mode 100644 index 0000000..2884587 --- /dev/null +++ b/slides/20240831pyconshizu/code/polly4.py @@ -0,0 +1,26 @@ +from contextlib import closing +from pathlib import Path +import boto3 + +polly = boto3.client('polly') # クライアント作成 + +# Lexiconを名前をつけて登録 +LEXICON = "tsubasaLexicon" +data = Path("tsubasa-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") + +with closing(result["AudioStream"]) as stream: + Path("hando-no-lexicon.mp3").write_bytes(stream.read()) + +# Lexiconを使用 +result = polly.synthesize_speech( + Text=s, OutputFormat="mp3", VoiceId="Takumi", + LexiconNames=[LEXICON]) + +with closing(result["AudioStream"]) as stream: + Path("hando-with-lexicon.mp3").write_bytes(stream.read()) diff --git a/slides/20240831pyconshizu/code/polly5.py b/slides/20240831pyconshizu/code/polly5.py new file mode 100644 index 0000000..a144661 --- /dev/null +++ b/slides/20240831pyconshizu/code/polly5.py @@ -0,0 +1,37 @@ +import re +from contextlib import closing +from pathlib import Path +import boto3 + + +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()) + + +polly = boto3.client('polly') + +# Lexiconを名前をつけて登録 +LEXICON = "tsubasaLexicon" +data = Path("tsubasa-lexicon.xml").read_text(encoding="utf-8") +polly.put_lexicon(Name=LEXICON, Content=data) + +# ファイルから問題文読み込み +p = Path("question.txt") +text = p.read_text(encoding="utf-8") + +with open("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") diff --git a/slides/20240831pyconshizu/code/question.txt b/slides/20240831pyconshizu/code/question.txt new file mode 100644 index 0000000..f8fc2fa --- /dev/null +++ b/slides/20240831pyconshizu/code/question.txt @@ -0,0 +1,4 @@ +次の日本語を英訳したときの()に当てはまる単語を選べ。 +「ボールはともだち」 +The ball is my () . +① father ② friend ③ enemy ④ neighbour diff --git a/slides/20240831pyconshizu/code/requirements.txt b/slides/20240831pyconshizu/code/requirements.txt new file mode 100644 index 0000000..30ddf82 --- /dev/null +++ b/slides/20240831pyconshizu/code/requirements.txt @@ -0,0 +1 @@ +boto3 diff --git a/slides/20240831pyconshizu/code/tsubasa-lexicon.xml b/slides/20240831pyconshizu/code/tsubasa-lexicon.xml new file mode 100644 index 0000000..02ca458 --- /dev/null +++ b/slides/20240831pyconshizu/code/tsubasa-lexicon.xml @@ -0,0 +1,29 @@ + + + + 反動蹴速迅砲 + ハンドウシュウソクジンホウ + + + S・G・G・K + スーパー・グレート・ゴール・キーパー + + + ()カッコ + + + まるいち + + + まるに + + + まるさん + + + まるよん + + diff --git a/slides/20240831pyconshizu/images/20240831pyconshizu.png b/slides/20240831pyconshizu/images/20240831pyconshizu.png new file mode 100644 index 0000000..1eb24b6 Binary files /dev/null and b/slides/20240831pyconshizu/images/20240831pyconshizu.png differ diff --git a/slides/20240831pyconshizu/images/lexicon-upload.png b/slides/20240831pyconshizu/images/lexicon-upload.png new file mode 100644 index 0000000..f7eae3c Binary files /dev/null and b/slides/20240831pyconshizu/images/lexicon-upload.png differ diff --git a/slides/20240831pyconshizu/images/polly-screen.png b/slides/20240831pyconshizu/images/polly-screen.png new file mode 100644 index 0000000..9e70f53 Binary files /dev/null and b/slides/20240831pyconshizu/images/polly-screen.png differ diff --git a/slides/20240831pyconshizu/images/polly-with-lexicon.png b/slides/20240831pyconshizu/images/polly-with-lexicon.png new file mode 100644 index 0000000..5b820bf Binary files /dev/null and b/slides/20240831pyconshizu/images/polly-with-lexicon.png differ diff --git a/slides/20240831pyconshizu/images/question.png b/slides/20240831pyconshizu/images/question.png new file mode 100644 index 0000000..1f5ab04 Binary files /dev/null and b/slides/20240831pyconshizu/images/question.png differ diff --git a/slides/20240831pyconshizu/images/slides-takanory-net.png b/slides/20240831pyconshizu/images/slides-takanory-net.png new file mode 100644 index 0000000..62a2d22 Binary files /dev/null and b/slides/20240831pyconshizu/images/slides-takanory-net.png differ diff --git a/slides/20240831pyconshizu/index.md b/slides/20240831pyconshizu/index.md new file mode 100644 index 0000000..7d2117c --- /dev/null +++ b/slides/20240831pyconshizu/index.md @@ -0,0 +1,594 @@ +```{eval-rst} +:og:image: _images/2024831pyconshizu.png +:og:image:alt: Amazon Pollyで問題a文を読み上げ + +.. |cover| image:: images/20240831pyconshizu.png +``` + +# **Amazon Polly** で
問題文を読み上げ **数式** も読み上げ + +Takanori Suzuki + +PyCon mini Shizuoka 2024 / 2024 Aug 31 + +## アジェンダ 📜 + +* 背景とゴール +* Amazon Pollyの基本 +* 読み上げをカスタマイズ +* 問題文読み上げでやったこと +* 数式読み上げ + +## 背景 🏞️ + +* 学習教材の **電子化** プロジェクト +* **合理的配慮** の一環としてのテキスト読み上げ +* 全盲の人向けではなく、**聴覚優位** の人向け + +### 合理的配慮 + +> 合理的配慮とは、障害者から何らかの助けを求める意思の表明があった場合、過度な負担になり過ぎない範囲で、社会的障壁を取り除くために必要な便宜のことである。 + +* [合理的配慮 - Wikipedia](https://ja.wikipedia.org/wiki/%E5%90%88%E7%90%86%E7%9A%84%E9%85%8D%E6%85%AE) + +### 聴覚優位 + +> 子どもたちの情報の取り入れ方を下記の3タイプに分類し、“知覚の優位性”という考え方が世界に広がっていったことに始まります。 +> +> 視覚優先型・聴覚優先型・運動感覚/触覚優先型 + +* [聴覚優位タイプとは?見るより聞くほうが理解しやすい子の勉強方法を専門家が解説](https://soctama.jp/column/67272) + +### やりたいこと + +* 問題文などを **読み上げ** られる + * **聴覚優位** の生徒が理解しやすく + * **数式** も読み上げられる +* 完全な読み上げじゃなくてもよい + +### ちなみに全盲の場合 + +* OSのアクセシビリティ機能を使う +* PC上のスクリーンリーダーを使用する +* Web側はアクセシビリティに対応する +* 参考: [ウェブアクセシビリティ導入ガイドブック|デジタル庁](https://www.digital.go.jp/resources/introduction-to-web-accessibility-guidebook/) +* →今回は対象外 + +## ゴール 🥅 + +* **Amazon Polly** での音声合成を知る +* Pythonでの **実装方法** を知る +* 読み上げの **カスタマイズ方法** を知る +* **数式を読み上げ** る方法を知る + +## Photos 📷 Tweets 🐦 👍 + +`#pyconshizu` / `@takanory` + +### Slides / スライド 💻 + +[slides.takanory.net](https://slides.takanory.net/) + +![slides.takanory.net](images/slides-takanory-net.png) + +## **Who** am I? / お前 **誰よ** 👤 + +* Takanori Suzuki / 鈴木 たかのり ({fab}`twitter` [@takanory](https://twitter.com/takanory)) +* [BeProud](https://www.beproud.jp/) 取締役 / Python Climber +* [PyCon JP Association](https://www.pycon.jp/) 代表理事 +* [Python Boot Camp](https://www.pycon.jp/support/bootcamp.html) 講師、[Python mini Hack-a-thon](https://pyhack.connpass.com/) 主催、[Pythonボルダリング部](https://kabepy.connpass.com/) 部長 + +![takanory profile](/assets/images/sokidan-square.jpg) +![kuro-chan and kuri-chan](/assets/images/kurokuri.jpg) + +### PyCon JP **Association** 🐍 + +日本国内のPythonユーザのために、**Pythonの普及及び開発支援**を行うために、継続的にカンファレンス(**PyCon**)を開くことを目的とした **非営利組織** + +[`www.pycon.jp`](https://www.pycon.jp) + +![pycon jp logo](/assets/images/pyconjp_logo.png) + +```{revealjs-break} +:notitle: +``` + +![PyCon JP 2024 logo](/assets/images/pyconjp2024logo.png) + +* {fas}`globe` [`2024.pycon.jp`](https://2024.pycon.jp/) +* 🎫 [PyCon JP 2024 - connpass](https://pyconjp.connpass.com/event/324211/) +* 🗓️ 9月27日(金)-29日(日) +* 🗼 東京、TOC有明 + +### **BeProud** Inc. 🏢 + +* [BeProud](https://www.beproud.jp/): Pythonシステム開発、コンサル +* [connpass](https://connpass.com/): IT勉強会支援プラットフォーム +* [PyQ](https://pyq.jp/): Python独学プラットフォーム +* [TRACERY](https://tracery.jp/): システム開発ドキュメントサービス + +![BeProud logos](/assets/images/beproud-logos.png) + +## Amazon Pollyの基本 🗣️ + +### Amaozon Polly + +* [Amazon Polly(深層学習を使用したテキスト読み上げサービス)| AWS](https://aws.amazon.com/jp/polly/) +* **数十の言語** で高品質で自然な人間の声を展開 +* 12ヶ月間、 毎月 **500万文字が無料** +* クラウド型コールセンターのAmazon Connectでも使える + +### Amazon Pollyの画面 + +* [テキスト読み上げ機能 | Amazon Polly](https://ap-northeast-1.console.aws.amazon.com/polly/home/SynthesizeSpeech) +* 画面でテキストを入力して読み上げできる + +```{image} images/polly-screen.png +:alt: Amazon Pollyの画面 +:width: 75% +``` + +```{revealjs-break} +``` + +* Amazon Pollyの画面からmp3をダウンロード +* 「ボールはともだち」([friend-ja.mp3](audio/friend-ja.mp3)) +* 「The ball is my friend」([friend-en.mp3](audio/friend-en.mp3)) + +### PythonからAmazon Pollyを実行 + +* [Boto3](https://boto3.amazonaws.com/v1/documentation/)をインストール +* APIを使うために環境変数を設定 + +```bash +$ 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 +``` + +```{revealjs-break} +``` + +```{literalinclude} code/polly.py +``` + +* [Polly - Boto3 1.34.56 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/polly.html) +* [AWSでAIサービスを使ってみる polly編](https://qiita.com/AInosukey/items/cb86c1012d40747b9dda) + +```{revealjs-break} +``` + +* mp3ファイルができた!! 🎉 +* [not-afraid.mp3](audio/not-afraid.mp3) + +```{literalinclude} code/polly.py +:lines: 6-10 +``` + +### Amazon Pollyの基本まとめ 🗣️ + +* **AWSの画面** から使える +* Pythonでは **Boto3** 経由で使える + +## 読み上げをカスタマイズ 🔧 + +### 言語の変更 + +* `VoiceId`引数で **言語と音声** を指定 +* 参考: [Amazon Polly の音声](https://docs.aws.amazon.com/ja_jp/polly/latest/dg/voicelist.html) + * 日本語: Mizuki, Takumi, Kazuha, Tomoko + * 英語: Ivy, Salli, Joey, Justinなど + +```{literalinclude} code/polly2.py +:lines: 7-9 +``` + +* [sggk.mp3](audio/sggk.mp3) + +### 読みの指定 + +* 「S・G・G・K」をちゃんと読ませたい +* `` タグでフリガナ指定 + * 参考: [発音記号を使用する](https://docs.aws.amazon.com/ja_jp/polly/latest/dg/supportedtags.html#phoneme-tag) +* 全体を `` タグで囲む +* `TextType="ssml"` 引数を追加 + + +```{revealjs-break} +``` + +```{literalinclude} code/polly3.py +:lines: 7-14 +``` + +* [super-great.mp3](audio/super-great.mp3) + +### SSMLタグ + +* **音声合成マークアップ言語(SSML)** に対応 + * 段落を区切る(`

`) + * 強調する(``) + * 音量、話す速度、ピッチ(``) + * 呼吸音(``)など +* 参考: + * [SSML ドキュメントから音声を生成する](https://docs.aws.amazon.com/ja_jp/polly/latest/dg/ssml.html) + * [サポートされている SSML タグ](https://docs.aws.amazon.com/ja_jp/polly/latest/dg/supportedtags.html) + +### 読み上げをカスタマイズまとめ 🔧 + +* **言語** と **音声** を変更できる +* **読み** を指定できる +* **SSMLタグ** でカスタマイズできる + +## **Lexicon** で読みをカスタマイズ 🛠️ + +### **Lexicon** とは + +* **発音レキシコン**: 発音の定義ファイル +* `` は **個別**、レキシコンは **共通** +* 複数ファイルを用意して使い分けも可能 +* 参考: [レキシコンの管理](https://docs.aws.amazon.com/ja_jp/polly/latest/dg/managing-lexicons.html) + +### Lexiconを使用する + +* Lexiconファイル(`tsubasa-lexicon.xml`)を用意 + +```{literalinclude} code/tsubasa-lexicon.xml +:lines: 1-13, 29 +``` + +### Amazon Pollyの画面でLexiconを使用 + +* 名前を付けてXMLファイルをアップロード +* レキシコンを使用する + +```{image} images/lexicon-upload.png +:alt: Lexiconをアップロード +:width: 75% +``` + +### PythonからLexiconを使用 + +```{literalinclude} code/polly4.py +:lines: 7-15, 20-24 +``` + +* [Lexiconなし](audio/hando-no-lexicon.mp3) +* [Lexiconあり](audio/hando-with-lexicon.mp3) + +### Lexiconで読みをカスタマイズまとめ 🛠️ + +* **Lexiconファイル** をXMLで作成 +* Lexiconファイルを **登録** +* 任意のLexiconを **適用** + +## 問題文読み上げでやったこと 📖 + +### **Lexicon** を作成 + +* ①、②:まるいち、まるに +* ()〔〕:括弧 +* 〜:から +* →:やじるし +* +:プラス +* ・:、 (句点と同じ空白が入る) + +### スペースを `` タグに + +* スペース部分を **一時停止タグ** に置換 +* 選択肢のラベルを区切って読む + +``` +〜〜を選べ ① ほげ ② ふが +``` + +↓ + +``` +〜〜を選べほげふが +``` + +### フリガナを `` タグに + +* 問題文はHTML形式 +* フリガナはHTMLの `` タグ + +```html +反動蹴速迅砲はんどうしゅうそくじんほう +``` + +↓ + +```html +反動蹴速迅砲 +``` + +### 日本語の英語の混ざった文章 + +* 日本語音声で英語を読ませると発音が**やばい** + * カタカナ英語みたいになる +* 英語の問題文は日本語と英語が **混ざっている** +* 日本語と英語に **分割** し音声読み上げ +* 1つのmp3にまとめる + +```{revealjs-break} +``` + +* 問題文の例(`question.txt`) + +```{literalinclude} code/question.txt +``` + +```{revealjs-break} +``` + +* ()、①を読ませるために **Lexicon** を登録 + +```{literalinclude} code/tsubasa-lexicon.xml +:lines: 14-22 +``` + +```{literalinclude} code/polly5.py +:lines: 20-23 +``` + +```{revealjs-break} +``` + +* 指定した言語で読み上げる **関数** + +```{literalinclude} code/polly5.py +:lines: 6-15 +``` + +```{revealjs-break} +``` + +* 正規表現で **日英を分割** して読み上げ + +```{literalinclude} code/polly5.py +:lines: 25-37 +``` + +```{revealjs-break} +``` + +* 日本語と英語の混ざった音声ができた!! 🎉 +* [question.mp3](audio/question.mp3) + +```{literalinclude} code/question.txt +``` + +### 問題文読み上げでやったことまとめ 📖 + +* Lexiconを作成 +* スペースを `` タグに +* フリガナを `` タグに +* 日英の混ざった文章対応 + +## 数式読み上げ 🧮 + +### 数式読み上げ 🧮 + +* 数学や理科の問題文には **数式** がでてくる +* 数式も **読み上げ** たい + +### 数式はどう表現している? + +* `$$` または `$` で数式を囲む + +```markdown +$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}$$ + +次の式を解きなさい $(5x+4)(5x+1)$ +``` + +$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}$$ + +次の式を解きなさい $(5x+4)(5x+1)$ + +### **どんな数式** が書ける? + +* LaTeXで書ける数式は全部できる(はず) +* 参考: [Easy Copy Mathjax](https://easy-copy-mathjax.nakaken88.com/) + +\begin{eqnarray} +\triangle ABC \equiv \triangle DEF +\end{eqnarray} + +\begin{eqnarray} +\varliminf_{ n \to \infty } A_n + = \bigcup_{ n = 1 }^{ \infty } \bigcap_{ k = n }^{ \infty } A_k + = \bigcup_{ n \in \mathbb{ N } } \bigcap_{ k \geqq n } A_k +\end{eqnarray} + +### どうやって **数式を表示** している? + +* 原稿はMarkdownの中に`$`、`$$`で囲んだ数式 +* Markdown変換: [markdown-it](https://github.com/markdown-it/markdown-it) +* Mathjax変換: [markdown-it-mathjax3](https://github.com/tani/markdown-it-mathjax3) + * markdown-itのプラグイン + * 内部では[mathjax-full](https://www.npmjs.com/package/mathjax-full)を使用 + +### 数式は **SVG** で表示されている + +$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}$$ + +```html + + +``` + +### SVGは画像フォーマットなので
読めない... 😇 + +## Mathjaxを読み上げる 💬 + +### MathJaxの **Accessibility機能** + +* [Accessibility Features - Screen Reader Support](https://docs.mathjax.org/en/latest/basic/accessibility.html#screen-reader-support) + + The `assistive-mml` extension embeds visually hidden MathML alongside MathJax's visual rendering while hiding the visual rendering from assistive technology (AT) such as screenreaders. + +### MathJaxの **Accessibility機能** + +* `assistive-mml`拡張によって視覚的なレンダリングの横に、**隠されたMathML**を埋め込む +* スクリーンリーダーなどはそのMathMLを読む + +### MathJaxの出力を **再確認** + +$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}$$ + +```{revealjs-literalinclude} code/mathml-sample.html +:data-line-numbers: 3-11 +``` + +### なんか読めそう!!👍 + +### **MathML** だけ取り出す + +```html + + x= # x = + + # 分数(fraction) + # 分子 + b± # -b± + # ルート(sqrt) + b2 # b 2乗 + 4ac # -4ac + + + 2a # 分母(2a) + + + +``` + +### **MathML** とは + +* 数式を記述するためのマークアップ言語 +* [Mathematical Markup Language - Wikipedia](https://ja.wikipedia.org/wiki/Mathematical_Markup_Language) +* [MathML の記述 - MathML | MDN](https://developer.mozilla.org/ja/docs/Web/MathML/Authoring) +* [MathML 要素リファレンス - MathML | MDN](https://developer.mozilla.org/ja/docs/Web/MathML/Element) + +### MathMLの **主な要素** + +* ``: 識別子(a, b, x, y等) +* ``: 演算子(+、-等) +* ``: 数字 +* ``: 分数 +* ``: ルート +* ``、``: 上付き、下付き +* ``: 弧で使用 + +## **読み上げテキスト** 作成 ✍️ + +### MathMLをBeautiful Soup 4で解析 🥣 + +* [Beautiful Soup](https://beautiful-soup-4.readthedocs.io/en/latest/): HTML、XMLのパーサー + +```bash +(venv) $ pip install beautifulsoup4 +``` + +```{literalinclude} code/polly-mathml.py +:lines: 5, 18-23, 53-55 +``` + +### **識別子**、**演算子** を変換 + +```{literalinclude} code/polly-mathml.py +:lines: 7-11, 16 +``` + +```{literalinclude} code/polly-mathml.py +:lines: 25-28 +``` + +### **分数** に対応 + +$\dfrac{1}{2}$ を「2ぶんの1」と読ませる + +```html +# MathML +12 +``` + +```{literalinclude} code/polly-mathml.py +:lines: 30-36 +``` + +### **2乗** に対応 + +$x^2$ を「x2乗」と読ませる + +```html +# MathML +x2 # x 2乗 +``` + +```{literalinclude} code/polly-mathml.py +:lines: 38-41 +``` + +### **ルート** に対応 + +$\sqrt{5}$ を「ルート5」と読ませる + +```html +# MathML +5 +``` + +```{literalinclude} code/polly-mathml.py +:lines: 43-45 +``` + +### 数式を変換してみる + +二次方程式 $ax^2 + bx + c = 0$ の解は + +$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}$$ + +```text +# 読み上げ用テキスト +二次方程式 ax2乗プラスbxプラスc=0 の解は +x=2aぶんのマイナスbプラスマイナスルートb2乗マイナス4ac +``` + +* [mathml.mp3](audio/mathml.mp3) + +### おしい... 😉 + +* ax → あっくす +* 2a → にあーる + +# まとめ 📚 + +* Amazon Pollyで音声合成は **簡単** にできる +* **多言語** に対応 +* **SSML**、**Lexicon** で細かい調整が可能 +* **数式** も **MathML** を解析して読み上げられる +* サンプルコード:{fab}`github` [code](https://github.com/takanory/slides/tree/master/slides/20240831pyconshizu/code) + +## Thank You 🙏 + +{fas}`desktop` [slides.takanory.net](https://slides.takanory.net/) + +{fab}`twitter` [@takanory](https://twitter.com/takanory) +{fab}`github` [takanory](https://github.com/takanory/) +{fab}`linkedin` [takanory](https://www.linkedin.com/in/takanory/) +{fab}`untappd` [takanory](https://untappd.com/user/takanory/) + +![takanory profile](/assets/images/sokidan-square.jpg) +![kuro-chan and kuri-chan](/assets/images/kurokuri.jpg) diff --git a/slides/assets/images/pyconjp2024logo.png b/slides/assets/images/pyconjp2024logo.png new file mode 100644 index 0000000..41a639f Binary files /dev/null and b/slides/assets/images/pyconjp2024logo.png differ