Amazon Polly をレキシコンによって音声合成する

レキシコンとは、Pollyにおける音声合成の際の読み上げで使う語彙集です。
Pollyで音声合成する時に、略語や特殊表記された内容を適切に読み上げられるように参照します。
レキシコンのファイルは、PLS(発音辞書仕様)という形式で記述します。拡張子は「.pls」です。

<?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>P2P</grapheme>
    <alias>ピアツーピア</alias>
  </lexeme>
  <lexeme>
    <grapheme>WWW</grapheme>
    <alias>ワールドワイドウェブ</alias>
  </lexeme>
</lexicon>

APIリファレンス

boto3  boto3.client関数

機能  デフォルトのセッションを使用して、低レベルのサービスクライアントを作成する
構文  boto3.client(クライアント名)
戻り値 サービスクライアントのオブジェクト

Polly  put_lexiconメソッド

機能
  AWSリージョンに発音レキシコンを保存します。同じ名前のレキシコンがリージョンにすでに存在する場合、
  新しいレキシコンによって上書きされます。
  レキシコン操作には結果整合性があるため、レキシコンがSynthesizeSpeech操作で使用できるようになるまでに
  時間がかかる場合があります。

構文

PUT /v1/lexicons/LexiconName HTTP/1.1
Content-type: application/json

{
   "Content": "string"
}

戻り値    HTTP/1.1 200

構文説明

URIリクエストパラメータ		
		
	[LexiconName]	
		
		レキシコンの名前。名前は、正規表現形式[0-9A-Za-z] { 1,20}に従う必要があります。
		つまり、名前は最大20文字の大文字と小文字を区別する英数字の文字列です。
		パターン: [0-9A-Za-z]{1,20}
		指定は必須
		
リクエストボディ		
		
	[Content]	
		
		文字列データとしてのPLSレキシコンのコンテンツ。
		タイプ:文字列
		指定は必須

戻り値説明  アクションが成功すると、サービスは空のHTTPボディを含むHTTP200応答を送り返します。

エラー内容

InvalidLexiconException	
	
	AmazonPollyは指定されたレキシコンを見つけることができません。
	レキシコンの名前のスペルが正しいことを確認してから、再試行してください。
	HTTPステータスコード:400
	
LexiconSizeExceededException	
	
	この操作では、指定されたレキシコンの最大サイズを超えます。
	HTTPステータスコード:400
	
MaxLexemeLengthExceededException	
	
	この操作では、語彙素の最大サイズを超えます。
	HTTPステータスコード:400
	
MaxLexiconsNumberExceededException	
	
	この操作では、レキシコンの最大数を超えています。
	HTTPステータスコード:400
	
ServiceFailureException	
	
	不明な状態が原因でサービス障害が発生しました。
	HTTPステータスコード:500
	
UnsupportedPlsAlphabetException	
	
	レキシコンで指定されたアルファベットは、サポートされているアルファベットではありません。
	HTTPステータスコード:400
	
UnsupportedPlsLanguageException	
	
	レキシコンで指定されている言語はサポートされていません。
	HTTPステータスコード:400

Polly  delete_lexiconメソッド

機能  AWSリージョンに保存されている指定された発音レキシコンを削除します。削除されたレキシコンは音声合成に使用できません。
    また、GetLexiconまたはListLexiconAPIを使用してレキシコンを取得することもできません。

リクエスト構文  DELETE /v1/lexicons/LexiconName HTTP/1.1

戻り値  HTTP/1.1 200

構文説明

URIリクエストパラメータ		
		
	[LexiconName]	
		
		削除するレキシコンの名前。リージョン内の既存のレキシコンである必要があります。
		パターン: [0-9A-Za-z]{1,20}
		指定は必須
		
リクエストボディ	リクエストにはリクエスト本文がありません。	

戻り値説明
  アクションが成功すると、サービスは空のHTTPボディを含むHTTP200応答を送り返します。

エラー内容

[LexiconNotFoundException]	
	
	AmazonPollyは指定されたレキシコンを見つけることができません。
	これは、レキシコンが欠落しているか、名前のスペルが間違っているか、別のリージョンにあるレキシコンを指定していることが
	原因である可能性があります。
	レキシコンが存在し、リージョン内にあり(ListLexiconsを参照)、名前のスペルが正しいことを確認します。その後、再試行してください。
	HTTPステータスコード:404
	
[ServiceFailureException]
	
	不明な状態が原因でサービス障害が発生しました。
	HTTPステータスコード:500

Polly  list_lexiconsメソッド

機能  AWSリージョンに保存されている発音レキシコンのリストを返します。

リクエスト構文  GET /v1/lexicons?NextToken=NextToken HTTP/1.1

戻り値

HTTP/1.1 200	
Content-type: application/json	
	
{	
   "Lexicons": [ 	
      { 	
         "Attributes": { 	
            "Alphabet": "string",	
            "LanguageCode": "string",	
            "LastModified": number,	
            "LexemesCount": number,	
            "LexiconArn": "string",	
            "Size": number	
         },	
         "Name": "string"	
      }	
   ],	
   "NextToken": "string"	
}	

構文説明

URIリクエストパラメータ		
		
	[NextToken]	
		
		前のListLexicons操作から返された不透明なページ付けトークン。存在する場合は、レキシコンのリストを続行する場所を示します。
		長さの制約:最小長=0 最大長=4096
		
リクエストボディ		リクエストにはリクエスト本文がありません。

戻り値説明

アクションが成功すると、サービスはHTTP200応答を送り返します。	
次のデータは、サービスによってJSON形式で返されます。	
	
[Lexicons]	
	
	レキシコンの名前と属性のリスト。
	タイプ:LexiconDescriptionオブジェクトの 配列
	
[NextToken]	
	
	レキシコンのリストを続行するために次のリクエストで使用するページネーショントークン。
	NextToken応答が切り捨てられた場合にのみ返されます。
	タイプ:文字列
	長さの制約:最小長=01 最大長=4096

エラー内容

[InvalidNextTokenException]	
	
	NextToken が無効です。スペルが正しいことを確認してから、もう一度やり直してください。
	HTTP ステータスコード: 400
	
[ServiceFailureException]	
	
	不明な状態が原因で、サービス障害が発生しました。
	HTTP ステータスコード: 500

Polly  get_lexiconsメソッド

機能  AWSリージョンに保存されている指定された発音レキシコンのコンテンツを返します。

リクエスト構文  GET /v1/lexicons/LexiconName HTTP/1.1

戻り値

HTTP/1.1 200	
Content-type: application/json	
	
{	
   "Lexicon": { 	
      "Content": "string",	
      "Name": "string"	
   },	
   "LexiconAttributes": { 	
      "Alphabet": "string",	
      "LanguageCode": "string",	
      "LastModified": number,	
      "LexemesCount": number,	
      "LexiconArn": "string",	
      "Size": number	
   }	
}	

構文説明

URIリクエストパラメータ		
		
	[LexiconName]	
		
		レキシコンの名前。
		パターン: [0-9A-Za-z]{1,20}
		指定は必須
		
リクエストボディ		リクエストにはリクエスト本文がありません。

戻り値説明

アクションが成功すると、サービスはHTTP200応答を送り返します。	
次のデータは、サービスによってJSON形式で返されます。	
	
[Lexicon]	
	
	レキシコンの名前と文字列の内容を提供するレキシコンオブジェクト。
	タイプ:レキシコンオブジェクト
	
[LexiconAttributes]	
	
	使用されている音声アルファベット、言語コード、レキシコンARN、レキシコンで定義されている語彙素の数、
	バイト単位のレキシコンのサイズなど、レキシコンのメタデータ。
	タイプ:LexiconAttributesオブジェクト

エラー内容

LexiconNotFoundException	
	
	AmazonPollyは指定されたレキシコンを見つけることができません。
	これは、レキシコンが欠落しているか、名前のスペルが間違っているか、別のリージョンにあるレキシコンを指定していることが
	原因である可能性があります。
	レキシコンが存在し、リージョン内にあり(ListLexiconsを参照)、名前のスペルが正しいことを確認します。その後、再試行してください。
	HTTPステータスコード:404
	
ServiceFailureException	
	
	不明な状態が原因でサービス障害が発生しました。
	HTTPステータスコード:500

①レキシコンを登録するプログラム  polly_put_lexicon.py

レキシコン「MyMikolabo」作成

import boto3
polly = boto3.client('polly')
with open('polly_lexicon.pls', 'r', encoding='utf-8') as file:
    polly.put_lexicon(Name='MyMikolabo', Content=file.read())
PS C:\Users\mikol\polly> python polly_put_lexicon.py

※ メッセージがなにも出力されなかったので、登録は成功!!

②レキシコンを使って音声合成するプログラム  polly_lexicon.py

レキシコン「MyMikolabo」を使って、音声合成する

import boto3
import contextlib
import os
polly = boto3.client('polly')
text = 'P2Pはネットワーク上に存在する端末(コンピューター)が、一対一の対等の関係で通信を行うことです。WWWとは無数の文書同士がハイパーリンクによって、結びついたネットワークのことです。'
result = polly.synthesize_speech(Text=text, OutputFormat='mp3', VoiceId='Mizuki', LexiconNames=['MyMikolabo'])
path = 'polly_lexicon_enabled.mp3'
with contextlib.closing(result['AudioStream']) as stream:
    with open(path, 'wb') as file:
        file.write(stream.read())
if os.name == 'nt':
    os.startfile(path)
PS C:\Users\mikol\polly> python polly_lexicon.py

※ プログラム起動すると、Windowsの音声メディアを扱うアプリケーションがしゃべります。
  P2Pをピアツーピアと読み上げ、wwwをワールドワイドウェブと発声します。

③レキシコンの一覧を取得するプログラム  polly_list_lexicon.py

import boto3
import pprint
polly = boto3.client('polly')
result = polly.list_lexicons()
pprint.pprint(result)
PS C:\Users\mikol\polly> python polly_list_lexicon.py

{'Lexicons': [{'Attributes': {'Alphabet': 'ipa',
                              'LanguageCode': 'ja',
                              'LastModified': datetime.datetime(2021, 7, 1, 17, 33, 8, 481000, tzinfo=tzlocal()),
                              'LexemesCount': 2,
                              'LexiconArn': 'arn:aws:polly:ap-northeast-1:999999999999:lexicon/MyMikolabo',
                              'Size': 525},
               'Name': 'MyMikolabo'}],
 'ResponseMetadata': {'HTTPHeaders': {'content-length': '239',
                                      'content-type': 'application/json',
                                      'date': 'Thu, 01 Jul 2021 08:49:47 GMT',
                                      'x-amzn-requestid': 'a22c3c0e-1385-4dd0-a367-999999999999'},
                      'HTTPStatusCode': 200,
                      'RequestId': 'a22c3c0e-1385-4dd0-a367-999999999999',
                      'RetryAttempts': 0}}

④指定したレキシコンデータを取得するプログラム  polly_get_lexicon.py

import boto3
import pprint
polly = boto3.client('polly')
result = polly.get_lexicon(Name='MyMikolabo')
pprint.pprint(result)
PS C:\Users\mikol\polly> python polly_get_lexicon.py

{'Lexicon': {'Content': '<?xml version="1.0" encoding="UTF-8"?>\n'
                        '<lexicon version="1.0" \n'
                        '  '
                        'xmlns="http://www.w3.org/2005/01/pronunciation-lexicon"\n'
                        '  '
                        'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" \n'
                        '  '
                        'xsi:schemaLocation="http://www.w3.org/2005/01/pronunciation-lexicon \n'
                        '    '
                        'http://www.w3.org/TR/2007/CR-pronunciation-lexicon-20071212/pls.xsd"\n'
                        '  alphabet="ipa" xml:lang="ja">\n'
                        '  <lexeme>\n'
                        '    <grapheme>P2P</grapheme>\n'
                        '    <alias>ピアツーピア</alias>\n'
                        '  </lexeme>\n'
                        '  <lexeme>\n'
                        '    <grapheme>WWW</grapheme>\n'
                        '    <alias>ワールドワイドウェブ</alias>\n'
                        '  </lexeme>\n'
                        '</lexicon>\n',
             'Name': 'MyMikolabo'},
 'LexiconAttributes': {'Alphabet': 'ipa',
                       'LanguageCode': 'ja',
                       'LastModified': datetime.datetime(2021, 7, 1, 17, 33, 8, 481000, tzinfo=tzlocal()),
                       'LexemesCount': 2,
                       'LexiconArn': 'arn:aws:polly:ap-northeast-1:999999999999:lexicon/MyMikolabo',
                       'Size': 525},
 'ResponseMetadata': {'HTTPHeaders': {'content-length': '828',
                                      'content-type': 'application/json',
                                      'date': 'Thu, 01 Jul 2021 08:56:30 GMT',
                                      'x-amzn-requestid': 'b38194f2-dbbd-4ab7-9dd4-999999999999'},
                      'HTTPStatusCode': 200,
                      'RequestId': 'b38194f2-dbbd-4ab7-9dd4-999999999999',
                      'RetryAttempts': 0}}

⑤レキシコンを削除するプログラム  polly_delete_lexicon.py

import boto3
polly = boto3.client('polly')
polly.delete_lexicon(Name='MyMikolabo')
PS C:\Users\mikol\polly> python polly_delete_lexicon.py
PS C:\Users\mikol\polly> python polly_list_lexicon.py

{'Lexicons': [],
 'ResponseMetadata': {'HTTPHeaders': {'content-length': '32',
                                      'content-type': 'application/json',
                                      'date': 'Thu, 01 Jul 2021 08:59:59 GMT',
                                      'x-amzn-requestid': 'd3a1257e-1b4c-4228-a716-999999999999'},
                      'HTTPStatusCode': 200,
                      'RequestId': 'd3a1257e-1b4c-4228-a716-999999999999',
                      'RetryAttempts': 0}}
PS C:\Users\mikol\polly> python polly_get_lexicon.py

Traceback (most recent call last):
  File "C:\Users\mikol\polly\polly_get_lexicon.py", line 4, in <module>
    result = polly.get_lexicon(Name='MyMikolabo')
  File "C:\Users\mikol\AppData\Roaming\Python\Python39\site-packages\botocore\client.py", line 386, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "C:\Users\mikol\AppData\Roaming\Python\Python39\site-packages\botocore\client.py", line 705, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.errorfactory.LexiconNotFoundException: An error occurred (LexiconNotFoundException) when calling the GetLexicon operation: Lexicon not found

※ レキシコンを削除したため、AmazonPollyは指定されたレキシコンを見つけることができません。