【Next.js AWS】音声文字起こし&要約、分析アプリケーション(S3)(Transcribe)(Amazon Bedrock)
更新日:2025/02/02

基本機能
- 音声ファイルをアップロード
- アップロードした音声を文字に変換(Transcribe)
- 変換したテキストをプロンプトを指定してBedrockで処理
- 結果表示
AWS、Next.jsで実装の理由
AWS SDKの充実
- Node.js用のSDKが完備
Next.jsのAPI Routes
- フロントエンドとバックエンドを統合できる
- APIエンドポイントの作成が簡単
AWSサービス間の連携
- S3、Transcribe、Bedrockが統一的な認証情報で利用可能
手順
Next.jsプロジェクトの作成
mkdir next-app; cd next-app; npx create-next-app@latest . --typescript --tailwind --app --src-dir --yes
フロントページ next-app\src\app\page.tsx
"use client";
import { useState, FormEvent } from "react";
export default function Home() {
const [file, setFile] = useState<File | null>(null);
const [prompt, setPrompt] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [jobName, setJobName] = useState<string | null>(null);
const [transcribeStatus, setTranscribeStatus] = useState<string | null>(null);
const [transcribeResult, setTranscribeResult] = useState<string | null>(null);
const [transcribeText, setTranscribeText] = useState<string | null>(null);
const [bedrockResult, setBedrockResult] = useState<string | null>(null);
// transcribeTextが設定された時にBedrockを呼び出す
const processWithBedrock = async (text: string) => {
try {
const response = await fetch("/api/bedrock", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ text, prompt }),
});
const data = await response.json();
if (data.result) {
setBedrockResult(data.result);
}
} catch (error) {
console.error("Bedrock処理エラー:", error);
}
};
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
if (!file) return;
setIsLoading(true);
try {
const formData = new FormData();
formData.append("file", file);
const response = await fetch("/api/upload", {
method: "POST",
body: formData,
});
const data = await response.json();
if (!response.ok) throw new Error(data.error);
if (data.jobName) {
setJobName(data.jobName);
checkTranscribeStatus(data.jobName);
}
} catch (error) {
console.error("エラー:", error);
} finally {
setIsLoading(false);
}
};
const checkTranscribeStatus = async (jobName: string) => {
try {
const response = await fetch(`/api/transcribe/status?jobName=${jobName}`);
const data = await response.json();
setTranscribeStatus(data.status);
if (data.status !== "COMPLETED") {
setTimeout(() => checkTranscribeStatus(jobName), 5000);
} else if (data.transcript) {
setTranscribeText(data.transcript);
// Bedrockでテキスト処理
await processWithBedrock(data.transcript);
}
} catch (error) {
console.error("ステータス確認エラー:", error);
}
};
return (
<div className="p-4 max-w-2xl mx-auto">
<h1 className="text-xl mb-4">音声文字起こし分析</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block mb-2">音声ファイルを選択</label>
<input
type="file"
accept="audio/*"
onChange={(e) => setFile(e.target.files?.[0] || null)}
className="w-full p-2 border"
required
/>
{file && <div className="mt-2 text-sm">選択中: {file.name}</div>}
</div>
<div>
<label className="block mb-2">プロンプト</label>
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
className="w-full p-2 border"
rows={3}
placeholder="例:要約してください、分析してください、重要なポイントを箇条書きにしてください" // プレースホルダーを具体的に
required
/>
</div>
<button
type="submit"
disabled={isLoading}
className="px-4 py-2 bg-gray-200 hover:bg-gray-300 disabled:bg-gray-100"
>
{isLoading ? "処理中..." : "処理開始"}
</button>
</form>
{/* 処理状態の表示 */}
{transcribeStatus && (
<div className="mt-4 p-4 bg-gray-50 rounded">
<h2 className="font-bold mb-2">処理状態</h2>
<p>{transcribeStatus}</p>
</div>
)}
{/* 処理結果の表示 */}
<div className="space-y-4">
{transcribeText && (
<div className="mt-4 p-4 bg-gray-50 rounded">
<h2 className="font-bold mb-2">文字起こし結果</h2>
<p className="whitespace-pre-wrap">{transcribeText}</p>
</div>
)}
{bedrockResult && (
<div className="mt-4 p-4 bg-gray-50 rounded border-t-4 border-blue-500">
<h2 className="font-bold mb-2">AIによる分析結果</h2>{" "}
{/* 「要約結果」から変更 */}
<p className="whitespace-pre-wrap">{bedrockResult}</p>
</div>
)}
</div>
</div>
);
}
AWS SDK のセットアップと S3 へのアップロード機能を実装
AWS SDK のインストール
npm install @aws-sdk/client-s3
既存ユーザーにAWS管理ポリシーを直接アタッチ
- IAMコンソール → ユーザー → 該当ユーザー
- 「許可を追加」→ 「既存のポリシーを直接アタッチ」
- 以下の管理ポリシーを選択:
AmazonS3FullAccess
AmazonTranscribeFullAccess
BedrockFullAccess
バケットの作成
next-app\src\app\api\upload\route.ts
import { NextResponse } from 'next/server'
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
import { TranscribeClient, StartTranscriptionJobCommand } from '@aws-sdk/client-transcribe'
// S3クライアントの初期化
const s3Client = new S3Client({
region: process.env.NEXT_PUBLIC_AWS_REGION || 'ap-northeast-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
}
})
// Transcribeクライアントの初期化
const transcribeClient = new TranscribeClient({
region: process.env.NEXT_PUBLIC_AWS_REGION || 'ap-northeast-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
}
})
export async function POST(request: Request) {
try {
const formData = await request.formData()
const file = formData.get('file') as File
if (!file) {
return NextResponse.json({ error: 'No file provided' }, { status: 400 })
}
// ファイルをArrayBufferに変換
const buffer = Buffer.from(await file.arrayBuffer())
// ファイル名に現在時刻を追加して一意にする
const fileKey = `uploads/${Date.now()}-${file.name}`
// S3にアップロード
await s3Client.send(
new PutObjectCommand({
Bucket: process.env.NEXT_PUBLIC_S3_BUCKET_NAME,
Key: fileKey,
Body: buffer,
ContentType: file.type,
})
)
// Transcribeジョブを開始
const jobName = `transcribe-${Date.now()}`
await transcribeClient.send(
new StartTranscriptionJobCommand({
TranscriptionJobName: jobName,
LanguageCode: 'ja-JP',
Media: {
MediaFileUri: `s3://${process.env.NEXT_PUBLIC_S3_BUCKET_NAME}/${fileKey}`
},
OutputBucketName: process.env.NEXT_PUBLIC_S3_BUCKET_NAME
})
)
return NextResponse.json({
success: true,
fileKey,
jobName,
bucket: process.env.NEXT_PUBLIC_S3_BUCKET_NAME
})
} catch (error) {
console.error('Upload error:', error)
return NextResponse.json({ error: 'Upload failed' }, { status: 500 })
}
}
next-app/.env.example
# AWS Credentials
AWS_ACCESS_KEY_ID=あなたのアクセスキーID
AWS_SECRET_ACCESS_KEY=あなたのシークレットアクセスキー
NEXT_PUBLIC_AWS_REGION=S3バケットにリージョン
NEXT_PUBLIC_S3_BUCKET_NAME=あなたのバケット名
Transcribeの設定
# Transcribe用のSDKをインストール
npm install @aws-sdk/client-transcribe
↓音声ファイル素材
next-app\src\app\api\transcribe\status\route.ts
import { NextResponse } from 'next/server'
import { TranscribeClient, GetTranscriptionJobCommand } from '@aws-sdk/client-transcribe'
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'
const transcribeClient = new TranscribeClient({
region: process.env.NEXT_PUBLIC_AWS_REGION || 'ap-northeast-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
}
})
const s3Client = new S3Client({
region: process.env.NEXT_PUBLIC_AWS_REGION || 'ap-northeast-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
}
})
export async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url)
const jobName = searchParams.get('jobName')
if (!jobName) {
return NextResponse.json({ error: 'Job name is required' }, { status: 400 })
}
const command = new GetTranscriptionJobCommand({
TranscriptionJobName: jobName
})
const response = await transcribeClient.send(command)
const job = response.TranscriptionJob
// デバッグログを追加
console.log('Transcribe Job Status:', job?.TranscriptionJobStatus)
console.log('Transcribe Job:', job)
// ジョブが完了していない場合は状態のみ返す
if (job?.TranscriptionJobStatus !== 'COMPLETED') {
return NextResponse.json({
status: job?.TranscriptionJobStatus
})
}
// 完了している場合は文字起こし結果を取得
try {
console.log('TranscriptFileUri:', job.Transcript?.TranscriptFileUri)
const transcriptPath = job.Transcript?.TranscriptFileUri
?.replace(`https://s3.${process.env.NEXT_PUBLIC_AWS_REGION}.amazonaws.com/${process.env.NEXT_PUBLIC_S3_BUCKET_NAME}/`, '');
console.log('Transcript Path:', transcriptPath)
if (transcriptPath) {
const s3Response = await s3Client.send(
new GetObjectCommand({
Bucket: process.env.NEXT_PUBLIC_S3_BUCKET_NAME!,
Key: transcriptPath
})
);
const transcriptJson = await s3Response.Body?.transformToString();
console.log('Transcript JSON:', transcriptJson)
const transcriptData = JSON.parse(transcriptJson || '{}');
return NextResponse.json({
status: 'COMPLETED',
transcript: transcriptData.results.transcripts[0].transcript
});
}
} catch (error) {
console.error('Error fetching transcript:', error);
}
return NextResponse.json({
status: 'COMPLETED',
error: 'Could not fetch transcript'
});
} catch (error) {
console.error('Status check error:', error)
return NextResponse.json({ error: 'Status check failed' }, { status: 500 })
}
}
Bedrockの処理
npm install @aws-sdk/client-bedrock-runtime
next-app\src\app\api\bedrock\route.ts
import { NextResponse } from 'next/server'
import { BedrockRuntimeClient, InvokeModelCommand } from '@aws-sdk/client-bedrock-runtime'
const bedrockClient = new BedrockRuntimeClient({
region: process.env.NEXT_PUBLIC_AWS_REGION || 'ap-northeast-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
}
})
export async function POST(request: Request) {
try {
const { text, prompt } = await request.json()
const response = await bedrockClient.send(
new InvokeModelCommand({
modelId: 'amazon.titan-text-express-v1',
contentType: 'application/json',
accept: 'application/json',
body: JSON.stringify({
inputText: `以下のテキストを${prompt}\n\n${text}`,
textGenerationConfig: {
maxTokenCount: 1000,
temperature: 0.7,
topP: 0.9,
stopSequences: []
}
})
})
)
const responseBody = JSON.parse(new TextDecoder().decode(response.body))
console.log('Bedrock response:', responseBody) // デバッグ用ログ
return NextResponse.json({ result: responseBody.results[0].outputText })
} catch (error) {
console.error('Bedrock error:', error)
return NextResponse.json({ error: 'Bedrock processing failed' }, { status: 500 })
}
}
実際のリクエストでは以下のように組み立てられています:
`以下のテキストを${prompt}\n\n${text}`
- “以下のテキストを” (固定文)
- “要約してください” (ユーザーが入力したプロンプト)
- 改行2回
- (文字起こしされたテキスト)
インフラ担当者への権限付与の依頼
件名:AWS権限設定依頼(S3, Transcribe, Bedrock)- 音声文字起こしアプリケーション用
お世話になっております。
音声文字起こしアプリケーション開発にあたり、以下のAWS権限の付与をお願いしたく存じます。
【目的】
音声ファイルのアップロードから文字起こし、AI分析までを行うアプリケーションの開発
【必要な権限】
1. AmazonS3FullAccess
- 用途:音声ファイルの保存、文字起こし結果の取得
2. AmazonTranscribeFullAccess
- 用途:音声の文字起こし処理
3. BedrockFullAccess
- 用途:文字起こしされたテキストのAI分析
【対象ユーザー/ロール】
- ユーザー名:[開発用IAMユーザー名]
ご確認のほど、よろしくお願いいたします。

-
検索
(例) debug search etc.. -
カテゴリー
新規記事
SEOサンプル記事
話題のキーワードから探す