Amazon PollyとAmazon Bedrockを使ってセルフAudibleやってみた
はじめに
みなさんどうもこんにちは!作業中に音楽や芸人のラジオなど聞くことありますよね。自分は特に洗い物中やスーパーで買い物している時にPodcastやラジオ、Youtubeのショートショートなど聞いたりしてます。そんな中便利なサービスとして「Audible」がありますよね。
自分は無料体験しかしたことないのですが、ビジネス書や小説、Podcastなど種類が多くとても良いサービスだと感じました。
しかし月額1500円で、自分としては他のサブスクより優先度が低かったためなかなかサブスクリプションまで踏み出すことができませんでした。
そこで今回は「Amazon Bedrock」にショートショートを考えさせ、テキストを音声に変換してくれるサービスである「Amazon Polly」を使って生成したショートショートをmp3に変換して作業中に聞くというセルフAudibleを試しクオリティを検証しました。
Amazon Pollyとは
テキストを音声に変換してくれるサービスで、深層学習を用いて人間が話すように流暢にテキストを読み上げてくれたり、強調効果によって特定の単語や文脈を強調して抑揚をつけて読み上げたりしてくれるサービスです。
構成
今回は検証として以下のリソースを用意しました。
- S3バケット: 「self-audible-bucket」
- Lambda : 「short-short-generator」
- Amazon Bedrock : boto3経由で「apac.anthropic.claude-3-7-sonnet-20250219-v1:0」を使用
- Amazon Polly : boto3経由で使用
Lambda用IAM Roleの作成
まずカスタムIAMポリシー「SelfAudiblePolicy」として以下のポリシーを生成します。
- bedrock: モデルの呼び出しと利用可能なモデル一覧を表示する権限
- polly:テキストを音声に変換する権限と利用可能な音声を一覧表示する権限
- S3 : バケットに対する操作全般
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:ListFoundationModels"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"polly:SynthesizeSpeech",
"polly:DescribeVoices"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::self-audible-bucket/*",
"arn:aws:s3:::self-audible-bucket"
]
}
]
}
次にIAMロール「SelfAudibleRole」を作成し、先ほど作成したIAMポリシーとCloudWatchロググループにログを残すために「AWSLambdaBasicExecutionRole」を付与します。
Bedrock上でのモデルアクセスの有効化
今回はモデルとして最近東京リージョンでも利用可能になった「Claude 3.7 Sonnet」を使用します。Bedrock上ではリージョンごとにモデルアクセスを有効化しないと使用できません。モデルの概要は以下のブログがわかりやすいです。
Amazon Bedrock画面のサイドバーから「モデルアクセス」を選択し、「モデルアクセスを変更」を押します。
「Claude 3.7 Sonnet」を選択し、会社名などのアンケート情報を入力して数分待ちます。
数分後「アクセスが付与されました」状態になると終了です。
Lambda関数の作成
今回は以下の設定で作成しました。
- Python 3.13
- x86_64ランタイム
- 実行ロールに先ほど作成したIAMロール「SelfAudibleRole」を指定
- メモリ: 512MB
- タイムアウト: 5分0秒
コードの内容
import boto3
import json
import os
S3_BUCKET_NAME = os.environ.get('S3_BUCKET_NAME')
BEDROCK_MODEL_ID = os.environ.get('BEDROCK_MODEL_ID')
POLLY_VOICE_ID = os.environ.get('POLLY_VOICE_ID', 'Takumi')
def lambda_handler(event, context):
# テーマとジャンルを取得
theme = event.get('theme', '未来')
genre = event.get('genre', 'SF')
story = generate_short_story(theme, genre)
title, content = extract_title_and_content(story)
# 音声変換
audio_url = text_to_speech(content, title)
return {
'statusCode': 200,
'body': json.dumps({
'title': title,
'story': content,
'audio_url': audio_url
}, ensure_ascii=False)
}
def generate_short_story(theme, genre):
bedrock = boto3.client('bedrock-runtime', region_name='ap-northeast-1')
# システムプロンプト
system_prompt = """あなたは優れたショートショート作家です。
条件:
- 800文字程度の短いショートショートにしてください
- 起承転結のある完結した物語にしてください
- 読者の心に残るような印象的な結末を用意してください
- 日本語で書いてください
必ず以下の形式で出力してください:
タイトル: [あなたが考えたタイトル]
[本文]
必ず「タイトル: 」で始まる形式を守ってください。
タイトルは短く、ファイル名に使えるようにしてください(特殊文字は避けてください)。"""
# メッセージ
user_message = f"""以下の条件で短編小説を書いてください。
テーマ: {theme}
ジャンル: {genre}"""
# リクエストの作成
request = {
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 4000,
"temperature": 0.7,
"system": system_prompt,
"messages": [
{
"role": "user",
"content": user_message
}
]
}
response = bedrock.invoke_model(
modelId=BEDROCK_MODEL_ID,
body=json.dumps(request)
)
response_body = json.loads(response['body'].read())
# ストーリー抽出
story = response_body['content'][0]['text']
return story
def extract_title_and_content(story):
lines = story.strip().split('\n')
title = ""
content = ""
# 最初の行が「タイトル:」で始まることを想定
if lines and lines[0].startswith("タイトル:"):
title = lines[0].replace("タイトル:", "").strip()
content = "\n".join(lines[1:]).strip()
else:
title = "無題のショートショート"
content = "\n".join(lines).strip()
return title, content
def text_to_speech(text, title):
polly = boto3.client('polly')
s3 = boto3.client('s3')
# ファイル名の作成
file_name = f"{title}.mp3"
# 音声合成リクエスト
response = polly.synthesize_speech(
Text=text,
OutputFormat="mp3",
VoiceId=POLLY_VOICE_ID,
Engine="neural" # ニューラル音声
)
# 一時ファイルに保存
temp_file = "/tmp/story.mp3"
try:
# 音声データをファイルに保存
with open(temp_file, "wb") as file:
file.write(response["AudioStream"].read())
# S3にアップロード
s3.upload_file(temp_file, S3_BUCKET_NAME, file_name)
# S3のURLを生成
s3_url = f"https://{S3_BUCKET_NAME}.s3.amazonaws.com/{file_name}"
return s3_url
except Exception as e:
print(f"音声変換エラー: {str(e)}")
raise e
finally:
# 一時ファイルを削除
if os.path.exists(temp_file):
os.remove(temp_file)
以下プロンプト内容
"あなたは優れたショートショート作家です。
条件:
- 800文字程度の短いショートショートにしてください
- 起承転結のある完結した物語にしてください
- 読者の心に残るような印象的な結末を用意してください
- 日本語で書いてください
必ず以下の形式で出力してください:
タイトル: [あなたが考えたタイトル]
[本文]
必ず「タイトル: 」で始まる形式を守ってください。
タイトルは短く、ファイル名に使えるようにしてください(特殊文字は避けてください)。"
最後に環境変数に以下を設定して準備完了です。
POLLY_VOICE_IDには「Amazon Polly」で使用できる音声が選択できます。
ショートショートの生成
実行時にイベントにテーマとジャンルを選択します。AIが汲み取ってくれるのでなんでもいいですが、今回は以下のように設定し、テストを実行しました。
{
"theme": "時間旅行",
"genre": "SF"
}
結果の内容
{
"statusCode": 200,
"body": {
"title": "時の扉",
"story": "「おじいちゃん、これは何?」孫の真也が、古い木箱に入った奇妙な懐中時計を見つけた。\n\n「それはな、時間を旅する時計だよ」と私は答えた。真也は目を輝かせる。子供の好奇心をくすぐる話だ。\n\n「嘘だー」と真也は笑う。\n\n「本当だよ。でも一つだけ決まりがある。過去は変えられない。どんなに辛くても、ただ見るだけなんだ」\n\n真也は半信半疑で時計を手に取った。「使い方は?」\n\n「針を回して日付を決め、ボタンを押すだけさ」\n\nその夜、私は真也が寝静まった後、こっそりと時計を取り出した。針を回し、30年前の日付に合わせる。ボタンを押すと、周囲が霞み、光の渦に包まれた。\n\n目が覚めると、そこは病院だった。ベッドに横たわる妻。そして30歳若い自分が泣きながら手を握っている。妻の最期の日だ。\n\n「あなた、約束して」かすれた声で妻が言う。「真也をしっかり育ててね」\n\n若い私は泣きながら頷いた。\n\n私は見えない存在として、その場面をただ見つめることしかできない。変えられないと知りながら、もう一度妻の声を聞きたかった。\n\n光の渦が再び現れ、私は現在に戻った。枕元には真也が立っていた。\n\n「おじいちゃん、泣いてるの?」\n\n「少し昔を思い出してね」と私は微笑んだ。\n\n「僕も試してみたよ。おばあちゃんに会えた」真也の目には涙が光っていた。「おばあちゃん、僕のこと知ってた。『真也、おじいちゃんを頼むね』って言ってた」\n\n驚いて顔を上げると、真也は続けた。「時間を変えられなくても、見守ることはできるんだね」\n\n私たちは黙って抱き合った。時計は静かに刻み続ける。過去は変えられなくても、記憶は未来へと流れ続けるのだから。",
"audio_url": "https://self-audible-bucket.s3.amazonaws.com/時の扉.mp3"
}
}
S3に「タイトル.mp3」が格納されていることを確認し、ダウンロードして聞いてみます。
正直うーんって感じです。
料金について
Lambda & S3の料金はほとんど0なので無視したとし、以下の条件で料金を出します。
- 1つのショートショートは約800文字
- 月に100本のショートショートを生成
- Claude 3.7 Sonnetへの入力は平均500トークン/本
- Claude 3.7 Sonnetからの出力は平均1,000トークン/本
- Pollyではニューラル音声を使用
Amazon Bedrock (Claude 3.7 Sonnet)
項目 | 単価 | 使用量 | 月額料金 |
---|---|---|---|
入力トークン | $0.003/1,000トークン | 50,000トークン | $0.15 |
出力トークン | $0.015/1,000トークン | 100,000トークン | $1.50 |
Bedrock 合計 | $1.65 |
Amazon Polly
音声タイプ | 単価 | 使用量 | 月額料金 |
---|---|---|---|
ニューラル音声 | $16.00/100万文字 | 80,000文字 | $1.28 |
総コスト (ニューラル音声使用時)
サービス | 月額料金 |
---|---|
Amazon Bedrock | $1.65 |
Amazon Polly (ニューラル音声) | $1.28 |
合計 | $2.93 |
話のクオリティを上げると、$3/月程度でショートショートが100本ほど作成できると考えるとお得ですね。
まとめ
今回は「Amazon Bedrock」を使ってショートショートのテキストをAIに生成してもらい、「Amazon Polly」を使って音声ファイルに変換して書き出してみました。10ファイルぐらい試しましたが、今回のプロンプトとモデルでは正直全く話に面白味がなく、ショートショートとして成立していないレベルでしたが、プロンプトの調整次第ではもっと面白くできそうです。
結論
星新一はすごい