このハンズオンでは、AWSのサーバレスサービスを活用して、ローカルのテキストファイルをアップロードし、翻訳・音声化・文字起こしを行うシステムを構築します。テキストから音声への変換(Polly)と、音声からテキストへの変換(Transcribe)の双方向変換を体験できます。
以下のAWSサービスを使用します。
フェーズ1: テキスト → 音声(Text-to-Speech)
フェーズ2: 音声 → テキスト(Speech-to-Text)
Positive : 本ハンズオンは初心者向けの内容です。AWSの基本的な操作ができれば問題ありません。
Negative : 本ハンズオンで作成するリソースには料金が発生する場合があります。ハンズオン終了後は必ずリソースを削除してください。
Codespace のターミナルで以下のコマンドを実行します。
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" unzip awscliv2.zip sudo ./aws/install rm -rf awscliv2.zip aws/ aws --version
aws configure
項目 | 値 |
AWS Access Key ID | 管理者から払い出されたキー |
AWS Secret Access Key | 管理者から払い出されたシークレット |
Default region name |
|
Default output format |
|
aws sts get-caller-identity
Negative : アクセスキーをGitリポジトリにコミットしないよう注意してください。
テキストファイルをアップロードするためのS3バケットを作成します。
以下の設定でバケットを作成します。
項目 | 値 |
バケット名 |
|
AWSリージョン |
|
その他設定 | デフォルトのまま |
翻訳結果と音声ファイルを格納するS3バケットを作成します。
同様の手順で以下の設定のバケットを作成します。
項目 | 値 |
バケット名 |
|
AWSリージョン |
|
その他設定 | デフォルトのまま |
Positive : S3バケット名はグローバルで一意である必要があります。アカウントIDを含めることで重複を避けましょう。
Lambda関数がS3、Translate、Polly、Transcribeにアクセスするためのロールを作成します。
以下の設定でロールを作成します。
項目 | 値 |
信頼されたエンティティ | AWS のサービス |
ユースケース | Lambda |
ロール名 |
|
以下のポリシーをアタッチします。
AmazonS3FullAccess
AmazonTranslateFullAccess
AmazonPollyFullAccess
AmazonTranscribeFullAccess
CloudWatchLogsFullAccess
Negative : 本ハンズオンでは簡易化のためFullAccessポリシーを使用しますが、本番環境では最小権限の原則に基づいたポリシーを設定してください。
同様の手順で、Step Functions用のロールを作成します。
項目 | 値 |
信頼されたエンティティ | AWS のサービス |
ユースケース | Step Functions |
ロール名 |
|
以下のポリシーをアタッチします。
AWSLambda_FullAccess
CloudWatchLogsFullAccess
項目 | 値 |
関数名 |
|
ランタイム | Python 3.12 |
実行ロール |
|
以下のコードを入力します。
import json
import boto3
import urllib.parse
s3 = boto3.client('s3')
def lambda_handler(event, context):
"""S3からテキストファイルを読み取る"""
bucket = event['bucket']
key = event['key']
# S3からファイルを取得
response = s3.get_object(Bucket=bucket, Key=key)
text = response['Body'].read().decode('utf-8')
return {
'statusCode': 200,
'bucket': bucket,
'key': key,
'text': text,
'source_language': 'ja'
}
新しいLambda関数を作成します。
項目 | 値 |
関数名 |
|
ランタイム | Python 3.12 |
実行ロール |
|
以下のコードを入力します。
import json
import boto3
translate = boto3.client('translate')
def lambda_handler(event, context):
"""テキストを英語に翻訳する"""
text = event['text']
source_language = event.get('source_language', 'ja')
target_language = 'en'
# Amazon Translateで翻訳
response = translate.translate_text(
Text=text,
SourceLanguageCode=source_language,
TargetLanguageCode=target_language
)
translated_text = response['TranslatedText']
return {
'statusCode': 200,
'bucket': event['bucket'],
'key': event['key'],
'original_text': text,
'translated_text': translated_text,
'source_language': source_language,
'target_language': target_language
}
Positive : Amazon Translateは多くの言語ペアに対応しています。source_languageとtarget_languageを変更することで、様々な言語間の翻訳が可能です。
新しいLambda関数を作成します。
項目 | 値 |
関数名 |
|
ランタイム | Python 3.12 |
実行ロール |
|
タイムアウト | 60秒(デフォルトの3秒から変更) |
以下のコードを入力します。
import json
import boto3
from datetime import datetime
polly = boto3.client('polly')
s3 = boto3.client('s3')
def lambda_handler(event, context):
"""翻訳されたテキストを音声に変換してS3に保存する"""
translated_text = event['translated_text']
output_bucket = event['bucket'].replace('input', 'output')
original_key = event['key']
# ファイル名を生成
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
output_key_text = f"translated/{timestamp}_{original_key}"
output_key_audio = f"audio/{timestamp}_{original_key.replace('.txt', '.mp3')}"
# 翻訳テキストをS3に保存
s3.put_object(
Bucket=output_bucket,
Key=output_key_text,
Body=translated_text.encode('utf-8'),
ContentType='text/plain'
)
# Amazon Pollyで音声合成
response = polly.synthesize_speech(
Text=translated_text,
OutputFormat='mp3',
VoiceId='Joanna',
Engine='neural'
)
# 音声ファイルをS3に保存
audio_stream = response['AudioStream'].read()
s3.put_object(
Bucket=output_bucket,
Key=output_key_audio,
Body=audio_stream,
ContentType='audio/mpeg'
)
return {
'statusCode': 200,
'output_bucket': output_bucket,
'translated_text_key': output_key_text,
'audio_key': output_key_audio,
'message': '翻訳テキストと音声ファイルの保存が完了しました'
}
Pollyで生成した音声をTranscribeで文字起こしするためのLambda関数を作成します。
新しいLambda関数を作成します。
項目 | 値 |
関数名 |
|
ランタイム | Python 3.12 |
実行ロール |
|
以下のコードを入力します。
import json
import boto3
from datetime import datetime
transcribe = boto3.client('transcribe')
def lambda_handler(event, context):
"""Pollyで生成した音声ファイルのTranscribeジョブを開始する"""
output_bucket = event['output_bucket']
audio_key = event['audio_key']
# ジョブ名を生成(一意にするためタイムスタンプを使用)
job_name = f"handson-transcribe-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
media_uri = f"s3://{output_bucket}/{audio_key}"
# Amazon Transcribeのジョブを開始
transcribe.start_transcription_job(
TranscriptionJobName=job_name,
Media={'MediaFileUri': media_uri},
MediaFormat='mp3',
LanguageCode='en-US',
OutputBucketName=output_bucket,
OutputKey=f"transcription/{job_name}.json"
)
return {
'statusCode': 200,
'output_bucket': output_bucket,
'audio_key': audio_key,
'transcription_job_name': job_name,
'translated_text_key': event.get('translated_text_key', ''),
'message': 'Transcribeジョブを開始しました'
}
Positive : Amazon Transcribeは非同期処理です。ジョブの開始後、完了まで数十秒〜数分かかる場合があります。Step Functions のWaitステートで自動的に待機します。
Transcribeジョブの結果を取得するLambda関数を作成します。
新しいLambda関数を作成します。
項目 | 値 |
関数名 |
|
ランタイム | Python 3.12 |
実行ロール |
|
以下のコードを入力します。
import json
import boto3
transcribe = boto3.client('transcribe')
s3 = boto3.client('s3')
def lambda_handler(event, context):
"""Transcribeジョブの結果を確認し、文字起こしテキストを取得する"""
job_name = event['transcription_job_name']
# ジョブのステータスを取得
response = transcribe.get_transcription_job(
TranscriptionJobName=job_name
)
status = response['TranscriptionJob']['TranscriptionJobStatus']
if status == 'COMPLETED':
# 結果をS3から取得
output_bucket = event['output_bucket']
result_key = f"transcription/{job_name}.json"
result_obj = s3.get_object(Bucket=output_bucket, Key=result_key)
result_data = json.loads(result_obj['Body'].read().decode('utf-8'))
# 文字起こしテキストを抽出
transcript_text = result_data['results']['transcripts'][0]['transcript']
# テキストファイルとしてS3に保存
summary_key = f"transcription/{job_name}_text.txt"
s3.put_object(
Bucket=output_bucket,
Key=summary_key,
Body=transcript_text.encode('utf-8'),
ContentType='text/plain'
)
return {
'statusCode': 200,
'transcription_status': 'COMPLETED',
'transcript_text': transcript_text,
'summary_key': summary_key,
'output_bucket': output_bucket,
'message': '文字起こしが完了しました'
}
elif status == 'FAILED':
raise Exception(f"Transcribeジョブが失敗しました: {response['TranscriptionJob'].get('FailureReason', '不明')}")
else:
# IN_PROGRESS の場合は再試行のためステータスを返す
return {
'statusCode': 200,
'transcription_status': status,
'transcription_job_name': job_name,
'output_bucket': event['output_bucket'],
'audio_key': event.get('audio_key', ''),
'translated_text_key': event.get('translated_text_key', ''),
'message': f'ジョブは {status} です。処理中...'
}
Positive : transcription_status の値によってStep Functionsが処理を分岐します。COMPLETED なら完了、それ以外なら待機→再確認のループになります。
以下のASL(Amazon States Language)を入力します。フェーズ1(テキスト→音声)とフェーズ2(音声→テキスト)の両方を含むワークフローです。
{
"Comment": "テキストファイルの翻訳・音声化・文字起こしワークフロー",
"StartAt": "ReadText",
"States": {
"ReadText": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-1:{アカウントID}:function:handson-read-text",
"Next": "TranslateText",
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "HandleError"
}
]
},
"TranslateText": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-1:{アカウントID}:function:handson-translate-text",
"Next": "TextToSpeech",
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "HandleError"
}
]
},
"TextToSpeech": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-1:{アカウントID}:function:handson-text-to-speech",
"Next": "StartTranscribe",
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "HandleError"
}
]
},
"StartTranscribe": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-1:{アカウントID}:function:handson-start-transcribe",
"Next": "WaitForTranscription",
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "HandleError"
}
]
},
"WaitForTranscription": {
"Type": "Wait",
"Seconds": 30,
"Next": "GetTranscription"
},
"GetTranscription": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-1:{アカウントID}:function:handson-get-transcription",
"Next": "CheckTranscriptionStatus",
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "HandleError"
}
]
},
"CheckTranscriptionStatus": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.transcription_status",
"StringEquals": "COMPLETED",
"Next": "SuccessState"
}
],
"Default": "WaitForTranscription"
},
"SuccessState": {
"Type": "Succeed"
},
"HandleError": {
"Type": "Fail",
"Error": "ProcessingError",
"Cause": "ワークフローの実行中にエラーが発生しました"
}
}
}
Negative : {アカウントID} は自分のAWSアカウントIDに置き換えてください。
項目 | 値 |
ステートマシン名 |
|
実行ロール |
|
ログ記録 | すべてのログ |
S3にテキストファイルがアップロードされた際に、自動的にStep Functionsを実行するよう設定します。
Positive : S3イベント通知からStep Functionsを直接起動するには、Amazon EventBridge経由で設定することもできます。
次に、EventBridgeでルールを作成します。
項目 | 値 |
ルール名 |
|
イベントバス | default |
ルールタイプ | イベントパターンを持つルール |
{
"source": ["aws.s3"],
"detail-type": ["Object Created"],
"detail": {
"bucket": {
"name": ["handson-input-{アカウントID}"]
},
"object": {
"key": [{
"suffix": ".txt"
}]
}
}
}
handson-translate-workflow を指定しますローカルにテスト用のテキストファイルを作成します。
echo "こんにちは。今日はAWSのサーバレスサービスを使ったハンズオンを行います。楽しんでいきましょう。" > test.txt
AWS CLIを使ってファイルをアップロードします。
aws s3 cp test.txt s3://handson-input-{アカウントID}/test.txt
WaitForTranscription → GetTranscription のループが発生します)translated/ フォルダ - 翻訳されたテキストファイルaudio/ フォルダ - 音声ファイル(.mp3)transcription/ フォルダ - 文字起こし結果(JSONとテキスト)aws s3 ls s3://handson-output-{アカウントID}/audio/
aws s3 cp s3://handson-output-{アカウントID}/audio/{生成されたファイル名}.mp3 ./output.mp3
ダウンロードしたMP3ファイルを再生して、英語の音声が出力されることを確認します。
aws s3 ls s3://handson-output-{アカウントID}/transcription/
aws s3 cp s3://handson-output-{アカウントID}/transcription/{ジョブ名}_text.txt ./transcription_result.txt
cat transcription_result.txt
文字起こしされた英語テキストが表示されます。翻訳されたテキストと比較して、Polly→Transcribeの変換精度を確認してみましょう。
Positive : 元の日本語テキスト → 英語翻訳テキスト → 英語音声 → 英語文字起こしテキスト の変換結果を比較することで、各サービスの変換精度を体感できます。
Positive : ワークフローがエラーになった場合は、Step Functionsの実行履歴からエラーの詳細を確認してください。Lambda関数のCloudWatch Logsも参考になります。
ハンズオンが完了したら、以下のリソースを削除して料金の発生を防ぎましょう。
削除順序は以下の通りです。
handson-s3-to-stepfunctions を削除handson-translate-workflow を削除handson-read-texthandson-translate-texthandson-text-to-speechhandson-start-transcribehandson-get-transcriptionhandson-input-{アカウントID}handson-output-{アカウントID}handson-lambda-rolehandson-stepfunctions-roleaws s3 rm s3://handson-input-{アカウントID} --recursive
aws s3 rm s3://handson-output-{アカウントID} --recursive
aws s3 rb s3://handson-input-{アカウントID}
aws s3 rb s3://handson-output-{アカウントID}
Negative : リソースの削除を忘れると、意図しない料金が発生する可能性があります。必ず全てのリソースを削除してください。
このハンズオンでは以下のことを学びました。
Matthew, Amy, Takumi)LanguageCode を変更して日本語音声の文字起こしを試してみましょう
[AWS Lambda ドキュメント](https://docs.aws.amazon.com/ja_jp/lambda/)
[AWS Step Functions ドキュメント](https://docs.aws.amazon.com/ja_jp/step-functions/)
[Amazon Translate ドキュメント](https://docs.aws.amazon.com/ja_jp/translate/)
[Amazon Polly ドキュメント](https://docs.aws.amazon.com/ja_jp/polly/)
[Amazon Transcribe ドキュメント](https://docs.aws.amazon.com/ja_jp/transcribe/)