🐕
OpenAI の Text to Speech API を使って音声を再生
はじめに
この記事では、OpenAI の Text to Speech API を使って、入力されたテキストを音声に変換して再生する方法を紹介します。
完成品
今回は、こちらを作成します。
リポジトリ
作業リポジトリはこちらです。
プロジェクトの作成
まずは、Next.js プロジェクトを作成します。
npx create-next-app@latest next-speech-synthesis-demo \
--typescript \
--eslint \
--import-alias "@/*" \
--src-dir \
--use-pnpm \
--tailwind \
--app \
--use-pnpm
cd next-speech-synthesis-demo
code .
OpenAI API キーの取得
こちらの記事を参考に、OpenAI API キーを取得します。
環境変数の設定
環境変数に OpenAI キーを追加します。<your-api-key>
に自身の API キーを設定してください。
$ touch .env .env.example
.gitignore
に .env
を追加します。
.gitignore
# local env files
.env*.local
+.env
.env
# OPENAI_API_KEY は OpenAI の API キーです。
OPENAI_API_KEY='<your-api-key>'
ついでに、.env.example
を作成します。
touch .env.example
.env.example
# OPENAI_API_KEY は OpenAI の API キーです。
OPENAI_API_KEY=
コミットします。
git add . && git commit -m "環境変数を作成"
OpenAI SDK のインストール
OpenAI SDK をインストールします。
pnpm add openai
コミットします。
git add . && git commit -m "OpenAI SDK をインストール"
Server Action を作成
テキストを引数に取り、音声を合成して base64 エンコードした文字列を返す synthesizeSpeech
関数を作成します。
touch src/app/actions.ts
actions.ts
'use server'
import OpenAI from 'openai'
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
})
export async function synthesizeSpeech(text: string): Promise<string> {
const mp3 = await openai.audio.speech.create({
model: "tts-1",
voice: "alloy",
input: text,
})
const buffer = Buffer.from(await mp3.arrayBuffer())
return buffer.toString('base64')
}
コミットします。
git add . && git commit -m "Server Action を作成"
コンポーネントを作成
2 つのコンポーネントを作成します。
mkdir -p src/components
touch src/components/demo-1.tsx
touch src/components/demo-2.tsx
demo-1.tsx
'use client'
import { useState } from 'react'
import { synthesizeSpeech } from '@/app/actions'
export const Demo1 = () => {
const [audioSrc, setAudioSrc] = useState<string | null>(null)
const [inputText, setInputText] = useState<string>('こんにちは!元気ですか!')
const [isLoading, setIsLoading] = useState<boolean>(false)
const handleSynthesize = async () => {
if (!inputText.trim()) return
setIsLoading(true)
try {
const base64Audio = await synthesizeSpeech(inputText)
setAudioSrc(`data:audio/mp3;base64,${base64Audio}`)
} catch (error) {
console.error('Error synthesizing speech:', error)
alert('Failed to synthesize speech. Please try again.')
} finally {
setIsLoading(false)
}
}
return (
<div className="flex flex-col items-center justify-center bg-gray-100 px-8 py-8 rounded-lg">
<h1 className="mb-6 self-start text-xl font-bold ">音声データを生成</h1>
<p className="mb-4 self-start text-slate-800">音声を生成したいテキストを入力してください</p>
<div className="w-full max-w-md space-y-4">
<div className="flex flex-row space-x-3">
<input
type="text"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="音声を生成したいテキストを入力してください"
className="w-full px-3 py-2 rounded-md text-slate-800"
/>
<button
onClick={handleSynthesize}
className="bg-slate-800 py-2 px-2 rounded-lg text-white w-[200px]"
disabled={isLoading || !inputText.trim()}
>
{isLoading ? '音声合成中...' : '音声を生成'}
</button>
</div>
{audioSrc && (
<audio controls src={audioSrc} className="w-full">
Your browser does not support the audio element.
</audio>
)}
</div>
</div>
)
}
demo-2.tsx
"use client";
import { useState, useCallback } from "react";
import { synthesizeSpeech } from "../app/actions";
export const Demo2 = () => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [inputText, setInputText] =
useState<string>("こんにちは!元気ですか!");
const playAudio = useCallback(async (base64Audio: string) => {
const AudioContextClass: typeof AudioContext =
window.AudioContext ||
(window as unknown as { webkitAudioContext: typeof AudioContext })
.webkitAudioContext;
const audioContext = new AudioContextClass();
const arrayBuffer = Uint8Array.from(atob(base64Audio), (c) =>
c.charCodeAt(0)
).buffer;
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContext.destination);
source.start(0);
}, []);
const handleSynthesize = async () => {
if (!inputText.trim()) return;
setIsLoading(true);
try {
const base64Audio = await synthesizeSpeech(inputText);
await playAudio(base64Audio);
} catch (error) {
console.error("Error synthesizing or playing speech:", error);
alert("Failed to synthesize or play speech. Please try again.");
} finally {
setIsLoading(false);
}
};
return (
<div className="flex flex-col items-center justify-center bg-gray-100 px-8 py-8 rounded-lg">
<h1 className="mb-6 self-start text-xl font-bold ">
音声データを即時再生
</h1>
<p className="mb-4 self-start text-slate-800">
音声を再生したいテキストを入力してください
</p>
<div className="w-full max-w-md space-y-4">
<div className="flex flex-row space-x-3">
<input
type="text"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="Enter text to synthesize"
aria-label="Text to synthesize"
className="w-full px-3 py-2 rounded-md text-slate-800"
/>
<button
onClick={handleSynthesize}
disabled={isLoading || !inputText.trim()}
className="bg-slate-800 py-2 px-2 rounded-lg text-white w-[200px]"
>
{isLoading ? '音声生成中...' : '音声再生'}
</button>
</div>
</div>
</div>
);
};
コミットします。
git add . && git commit -m "コンポーネントを作成"
ページを作成
page.tsx
を作成します。
page.tsx
import { Demo1 } from "@/components/demo-1";
import { Demo2 } from "@/components/demo-2";
export default function Home() {
return (
<div className="flex flex-col items-center justify-center min-h-screen space-y-4">
<Demo1 />
<Demo2 />
</div>
);
}
コミットします。
git add . && git commit -m "ページを作成"
動作確認
動作確認をします。
pnpm run dev
まとめ
この記事では、OpenAI の Text to Speech API を使って、入力されたテキストを音声に変換して再生する方法を紹介しました。
Discussion