feat: use VOICEVOX API for TTS

This commit is contained in:
2025-06-04 11:26:55 +09:00
parent b8d53f31c7
commit 7193dd8bb7

View File

@ -1,37 +1,69 @@
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import { PollyClient, SynthesizeSpeechCommand } from "@aws-sdk/client-polly"; import fetch from "node-fetch";
const polly = new PollyClient({ region: "ap-northeast-1" }); // VOICEVOX APIの設定
const VOICEVOX_HOST = "http://localhost:50021";
interface VoiceStyle {
speakerId: number;
styleId: number;
}
// 仮の声設定(例: サイドM = 3
const defaultVoiceStyle: VoiceStyle = {
speakerId: 3,
styleId: 2,
};
export async function generateTTS( export async function generateTTS(
itemId: string, itemId: string,
scriptText: string, scriptText: string,
): Promise<string> { ): Promise<string> {
const params = { // 音声合成クエリの生成
OutputFormat: "mp3", const queryResponse = await fetch(`${VOICEVOX_HOST}/audio_query`, {
Text: scriptText, method: "POST",
VoiceId: "Mizuki", headers: {
LanguageCode: "ja-JP", "Content-Type": "application/json",
}; },
const command = new SynthesizeSpeechCommand(params); body: JSON.stringify({
const response = await polly.send(command); text: scriptText,
speaker: defaultVoiceStyle.speakerId,
}),
});
if (!response.AudioStream) { if (!queryResponse.ok) {
throw new Error("TTSのAudioStreamが空です"); throw new Error("VOICEVOX 音声合成クエリ生成に失敗しました");
} }
const audioQuery = await queryResponse.json();
// 音声合成
const audioResponse = await fetch(`${VOICEVOX_HOST}/synthesis`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
audio_query: audioQuery,
speaker: defaultVoiceStyle.speakerId,
}),
});
if (!audioResponse.ok) {
throw new Error("VOICEVOX 音声合成に失敗しました");
}
const audioBuffer = await audioResponse.buffer();
// 出力ディレクトリの準備
const outputDir = path.join(__dirname, "../public/podcast_audio"); const outputDir = path.join(__dirname, "../public/podcast_audio");
if (!fs.existsSync(outputDir)) { if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true }); fs.mkdirSync(outputDir, { recursive: true });
} }
const filePath = path.resolve(outputDir, `${itemId}.mp3`);
const chunks: Uint8Array[] = []; const filePath = path.resolve(outputDir, `${itemId}.mp3`);
for await (const chunk of response.AudioStream as any) { fs.writeFileSync(filePath, audioBuffer);
chunks.push(chunk);
}
const buffer = Buffer.concat(chunks);
fs.writeFileSync(filePath, buffer);
return filePath; return filePath;
} }