【Nextjs】【TypeScript】テキストを音声に変換するアプリを構築する(完成編)
まえがき
前回に引き続き、Google CloudのText-to-Speech AIを使ってテキストを音声に変換するアプリを構築する続きをします。quickStart()
に値を渡すために引数を追加します。
そしてアプリの完成まで終わらせます。
場合は自己責任でお願いします。
開発環境
入力画面(UI)を作成
daisyUIを導入
今回はTailwind CSSのコンポーネントライブラリのdaisyUIを使用します。
daisyUIをインストール
以下のコマンドを実行してインストールします。
npm i -D daisyui@latest
tailwind.config.js
にplugins
にdaisyUIを追加します。
- plugins: [],
+ plugins: [require("daisyui")],
入力画面(UI)を作成
UIのイメージは以下の通りです。
簡単に説明すると音声に変換するテキストを入力するテキストエリアと置いて、その下に音声ファイル名を入力するインプットエリアと音声変換を実行するボタンを並べています。
コードは以下通りです。
export default function Home() {
return (
<main className="bg-gray-200 w-full h-screen overflow-hidden">
<div className="max-w-2xl mx-auto h-full">
<h1 className="text-3xl text-center py-4">Text Recording App</h1>
<form className="h-full">
<textarea
required
className="textarea w-full h-2/3 mb-4"
placeholder="音声変換するテキストを入力"
></textarea>
<div className="flex">
<input
type="text"
placeholder="音声ファイル名"
required
className="input w-1/2 mr-4"
/>
<button type="submit" className="btn btn-neutral w-1/2">
<span className="text-xl tracking-widest">音声変換実行</span>
</button>
</div>
</form>
</div>
</main>
);
}
ちなみに各パーツのサイズや配置などはお好みでOKです。
quickStart()
に引数を追加
関数引数を設定
入力画面で入力した音声に変換するテキストと音声ファイル名を受け取れるように引数を設定します。
テキストの引数はinputText
、ファイル名の引数はfileName
にします。
両方の引数の型はstring
になります。
- export async function quickStart() {
+ export async function quickStart(inputText: string, fileName: string) {
text
を削除
変数変数text
は不要なので削除します。
- const text = "hello, world!";
新たな引数をコード内に設置
const request: textToSpeech.protos.google.cloud.texttospeech.v1.ISynthesizeSpeechRequest =
{
- input: { text: text },
+ input: { text: inputText },
voice: { languageCode: "en-US", ssmlGender: "NEUTRAL" },
audioConfig: { audioEncoding: "MP3" },
};
- await writeFile("output.mp3", response.audioContent as Buffer, "binary");
- console.log("Audio content written to file: output.mp3");
+ await writeFile(`${fileName}.mp3`, response.audioContent as Buffer, "binary");
+ console.log(`Audio content written to file: ${fileName}.mp3`);
関数名を変更
関数の機能がわかりやすくするように関数の名前をquickStart()
からファイル名と同じtextRecording()
に変更します。
- export async function quickStart(inputText: string, fileName: string) {
+ export async function textRecording(inputText: string, fileName: string) {
ここまでのtextRecording.tsのコードは以下の通りです。
utils/textRecording.ts
import * as textToSpeech from "@google-cloud/text-to-speech";
import fs from "fs";
import util from "util";
const option = {
keyFilename: "secret.json",
};
export async function textRecording(inputText: string, fileName: string) {
const client = new textToSpeech.TextToSpeechClient(option);
const request: textToSpeech.protos.google.cloud.texttospeech.v1.ISynthesizeSpeechRequest =
{
input: { text: inputText },
voice: { languageCode: "en-US", ssmlGender: "NEUTRAL" },
audioConfig: { audioEncoding: "MP3" },
};
const [response] = await client.synthesizeSpeech(request);
const writeFile = util.promisify(fs.writeFile);
await writeFile(`${fileName}.mp3`, response.audioContent as Buffer, "binary");
console.log(`Audio content written to file: ${fileName}.mp3`);
}
textRecording()
に入力データを渡す
入力画面(UI)から関数入力されたデータを取得
まずは<textarea>
タグに入力されたデータを取得するにはonChange
イベントを使います。
以下のようにコードを追加します。
<textarea
required
className="textarea w-full h-2/3 mb-4"
placeholder="音声変換するテキストを入力"
+ onChange={(e) => console.log(e.target.value)}
></textarea>
データが入力されるたびイベントe
が発生します。
e
は任意の変数なので、わかりやすいようにevent
に置き換えてもOKです。
そのイベントを受け取るとe.target.value
に入力データが格納されます。
そしてconsole.log()
でデータを確認します。
詳しくは以下のドキュメントを見てください。
<textarea>
タグと同じように<input>
タグにもコードを追加します。
<input
type="text"
placeholder="音声ファイル名"
required
className="input input-bordered w-full max-w-xs mx-4"
+ onChange={(e) => console.log(e.target.value)}
/>
詳しくは以下のドキュメントを見てください。
useState
を使って入力したデータを変数に格納
<textarea>
タグや<input>
タグの入力データはe.target.value
で取得できました。
しかし、同じ値から取得することになるので、どのタグからの入力データなのかわかりません。
そこで、useState
というStateフックという機能を使います。
app/page.tsx
の冒頭からコードを追加します。
+ "use client";
+ import { useState } from "react";
export default function Home() {
+ const [inputText, setInputText] = useState<string>("");
+ const [fileName, setFileName] = useState<string>("");
追加したコードを見ていきます。
まずは"use client"
を宣言します。
このコンポーネントはクライアントで動作させることを宣言することになります。
次はuseState
をreact
からインポートします。
最後は各入力タグ毎にuseState
を追加します。
ここではinputText
を例に見ます。
const [inputText, setInputText] = useState<string>("");
inputText
はstate
変数であり、setInputText
はセッタ関数です。
useState
に格納されるデータの型はstring
なのでuseState<string>
のように型を指定します。
その後の()
内にはinputText
の初期値を指定しています。
useState
の動きとしては、inputText
には初期値の""
が格納されます。
そしてsetInputText
を使って新たなデータをinputText
に上書きされます。
具体的には以下のようにコードを変更します。
<textarea
required
className="textarea textarea-bordered w-full h-2/3 mb-4"
placeholder="音声変換するテキストを入力"
- onChange={(e) => console.log(e.target.value)}
+ onChange={(e) => setInputText(e.target.value)}
></textarea>
console.log()
では入力データをコンソールで見ましたが、それをsetInputText()
の引数としてe.target.value
を渡してinputText
を更新します。
これでinputText
をtextRecording()
の引数として使うことできます。
同様にinput
タグ内も変更します。
<input
type="text"
placeholder="音声ファイル名"
required
className="input w-1/2 mr-4"
- onChange={(e) => console.log(e.target.value)}
+ onChange={(e) => setFileName(e.target.value)}
/>
念のためにconsole.log()
で動作を確認します。
const [inputText, setInputText] = useState<string>("");
const [fileName, setFileName] = useState<string>("");
+ console.log(inputText);
+ console.log(fileName);
動作確認が終わったら、console.log()
は削除かコメントアウトしておいてください。
useState
を詳しく知りたい方は以下のドキュメントをご覧ください。
textRecording()
の引数として渡す
入力したデータを関数useState
でデータを格納した変数をtextRecording()
の引数として渡します。
まずはtextRecording()
の挙動を考えます。
textRecording()
はボタンをされた時に実行されるようにしなければいけません。
そのためには、form
タグにonSubmit
イベントを追加して関数handleSubmit
を指定します。
- <form className="h-full">
+ <form className="h-full" onSubmit={handleSubmit}>
関数handleSubmit
はないのでuseState
の後にコードを追加します。
export default function Home() {
const [inputText, setInputText] = useState<string>("");
const [fileName, setFileName] = useState<string>("");
+ const handleSubmit = async () => {
+ await textRecording(textInput, fileName);
+ };
Server Actions機能
関数handleSubmit
を作成したらエラーが発生しました。
textRecording()
で使われているモジュールfs
はサーバー上で動かします。
ただapp/page.tsx
は"use client"
を宣言しているのでクライアントコンポーネントになります。
クライアントコンポーネント内ではモジュールfs
は使えません。
そこでServer Actionsという機能を使います。
next.config.js
に設定を追加
next.config.js
に設定を追加します。
/** @type {import('next').NextConfig} */
const nextConfig = {
+ experimental: {
+ serverActions: true,
+ },
};
module.exports = nextConfig;
"use server"
を宣言
"use server"
をutils/textRecording.ts
の1番上に追加します。
+ "use server"
あとは以下が参考にしたドキュメントとサイトです。
動作確認
ボタンが押されると関数handleSubmit
が発火します。
そして関数handleSubmit
でtextRecording
にtextInput
とfileName
を引数として渡して実行されます。
すると音声ファイルが作成されます。
今回のコード
これまでのコードは以下の通りです。
app/page.tsx
"use client";
import { textRecording } from "@/utils/textRecording";
import { useState } from "react";
export default function Home() {
const [textInput, setTextInput] = useState<string>("");
const [fileName, setFileName] = useState<string>("");
const handleSubmit = async () => {
await textRecording(textInput, fileName);
};
return (
<main className="bg-gray-200 w-full h-screen">
<div className="max-w-2xl mx-auto h-full">
<h1 className="text-3xl text-center py-4">Text to Speech App</h1>
<form className="h-full" onSubmit={handleSubmit}>
<textarea
required
className="textarea textarea-bordered w-full h-2/3 mb-4"
placeholder="音声変換するテキストを入力"
onChange={(e) => setTextInput(e.target.value)}
></textarea>
<div className="flex">
<input
type="text"
placeholder="音声ファイル名"
required
className="input input-bordered w-full max-w-xs mr-4"
onChange={(e) => setFileName(e.target.value)}
/>
<button type="submit" className="btn btn-neutral w-1/2">
<span className="text-xl tracking-widest">音声変換実行</span>
</button>
</div>
</form>
</div>
</main>
);
}
utils/textRecording.ts
"use server"
import * as textToSpeech from "@google-cloud/text-to-speech";
import fs from "fs";
import util from "util";
const option = {
keyFilename: "secret.json",
};
export async function textRecording(inputText: string, fileName: string) {
const client = new textToSpeech.TextToSpeechClient(option);
const request: textToSpeech.protos.google.cloud.texttospeech.v1.ISynthesizeSpeechRequest =
{
input: { text: inputText },
voice: { languageCode: "en-US", ssmlGender: "NEUTRAL" },
audioConfig: { audioEncoding: "MP3" },
};
const [response] = await client.synthesizeSpeech(request);
const writeFile = util.promisify(fs.writeFile);
await writeFile(`${fileName}.mp3`, response.audioContent as Buffer, "binary");
console.log(`Audio content written to file: ${fileName}.mp3`);
}
次回
これで目的のアプリが完成できました。
ただ、前回の記事に載せていたPythonのコードを見ていただくとわかりますが、完全に移植できたわけではありません。
改行で文章を分けてssml
タグを利用したり、neural2
音声を利用して変換したりとすべて実装できていません。
そこで次回はneural2
音声を利用できるように拡張します。
スマホアプリ「ひとこと投資メモ」シリーズをリリース
記事とは関係ないことですが、最後にお知らせです。
Flutter学習のアウトプットの一環として「日本株ひとこと投資メモ」「米国株ひとこと投資メモ」を公開しています。
簡単に使えるライトな投資メモアプリです。
iPhone、Android両方に対応しています。
みなさんの投資ライフに少しでも活用していただきれば幸いです。
以下のリンクからそれぞれのサイトに移動してダウンロードをお願いします。
Discussion