Fix
This commit is contained in:
165
services/tts.ts
165
services/tts.ts
@ -30,94 +30,87 @@ export async function generateTTS(
|
|||||||
const queryUrl = `${config.voicevox.host}/audio_query?text=${encodedText}&speaker=${defaultVoiceStyle.styleId}`;
|
const queryUrl = `${config.voicevox.host}/audio_query?text=${encodedText}&speaker=${defaultVoiceStyle.styleId}`;
|
||||||
const synthesisUrl = `${config.voicevox.host}/synthesis?speaker=${defaultVoiceStyle.styleId}`;
|
const synthesisUrl = `${config.voicevox.host}/synthesis?speaker=${defaultVoiceStyle.styleId}`;
|
||||||
|
|
||||||
try {
|
const queryResponse = await fetch(queryUrl, {
|
||||||
const queryResponse = await fetch(queryUrl, {
|
method: "POST",
|
||||||
method: "POST",
|
headers: {
|
||||||
headers: {
|
"Content-Type": "application/json",
|
||||||
"Content-Type": "application/json",
|
Accept: "application/json",
|
||||||
Accept: "application/json",
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (!queryResponse.ok) {
|
if (!queryResponse.ok) {
|
||||||
const errorText = await queryResponse.text();
|
const errorText = await queryResponse.text();
|
||||||
throw new Error(
|
|
||||||
`VOICEVOX audio query failed (${queryResponse.status}): ${errorText}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const audioQuery = await queryResponse.json();
|
|
||||||
|
|
||||||
console.log(`音声合成開始: ${itemId}`);
|
|
||||||
const audioResponse = await fetch(synthesisUrl, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(audioQuery),
|
|
||||||
signal: AbortSignal.timeout(10000000), // タイムアウトを10分に設定
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!audioResponse.ok) {
|
|
||||||
const errorText = await audioResponse.text();
|
|
||||||
console.error(`音声合成失敗: ${itemId}`);
|
|
||||||
throw new Error(
|
|
||||||
`VOICEVOX synthesis failed (${audioResponse.status}): ${errorText}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const audioArrayBuffer = await audioResponse.arrayBuffer();
|
|
||||||
const audioBuffer = Buffer.from(audioArrayBuffer);
|
|
||||||
|
|
||||||
// 出力ディレクトリの準備
|
|
||||||
const outputDir = config.paths.podcastAudioDir;
|
|
||||||
if (!fs.existsSync(outputDir)) {
|
|
||||||
fs.mkdirSync(outputDir, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
const wavFilePath = path.resolve(outputDir, `${itemId}.wav`);
|
|
||||||
const mp3FilePath = path.resolve(outputDir, `${itemId}.mp3`);
|
|
||||||
|
|
||||||
console.log(`WAVファイル保存開始: ${wavFilePath}`);
|
|
||||||
fs.writeFileSync(wavFilePath, audioBuffer);
|
|
||||||
console.log(`WAVファイル保存完了: ${wavFilePath}`);
|
|
||||||
|
|
||||||
console.log(`MP3変換開始: ${wavFilePath} -> ${mp3FilePath}`);
|
|
||||||
|
|
||||||
const ffmpegCmd = ffmpegPath || "ffmpeg";
|
|
||||||
const result = Bun.spawnSync({
|
|
||||||
cmd: [
|
|
||||||
ffmpegCmd,
|
|
||||||
"-i",
|
|
||||||
wavFilePath,
|
|
||||||
"-codec:a",
|
|
||||||
"libmp3lame",
|
|
||||||
"-qscale:a",
|
|
||||||
"2",
|
|
||||||
"-y", // Overwrite output file
|
|
||||||
mp3FilePath,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.exitCode !== 0) {
|
|
||||||
const stderr = result.stderr
|
|
||||||
? new TextDecoder().decode(result.stderr)
|
|
||||||
: "Unknown error";
|
|
||||||
throw new Error(`FFmpeg conversion failed: ${stderr}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wavファイルを削除
|
|
||||||
if (fs.existsSync(wavFilePath)) {
|
|
||||||
fs.unlinkSync(wavFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`TTS生成完了: ${itemId}`);
|
|
||||||
|
|
||||||
return path.basename(mp3FilePath);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error generating TTS:", error);
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to generate TTS: ${error instanceof Error ? error.message : "Unknown error"}`,
|
`VOICEVOX audio query failed (${queryResponse.status}): ${errorText}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const audioQuery = await queryResponse.json();
|
||||||
|
|
||||||
|
console.log(`音声合成開始: ${itemId}`);
|
||||||
|
const audioResponse = await fetch(synthesisUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(audioQuery),
|
||||||
|
signal: AbortSignal.timeout(600000), // 10分のタイムアウト
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!audioResponse.ok) {
|
||||||
|
const errorText = await audioResponse.text();
|
||||||
|
console.error(`音声合成失敗: ${itemId}`);
|
||||||
|
throw new Error(
|
||||||
|
`VOICEVOX synthesis failed (${audioResponse.status}): ${errorText}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const audioArrayBuffer = await audioResponse.arrayBuffer();
|
||||||
|
const audioBuffer = Buffer.from(audioArrayBuffer);
|
||||||
|
|
||||||
|
// 出力ディレクトリの準備
|
||||||
|
const outputDir = config.paths.podcastAudioDir;
|
||||||
|
if (!fs.existsSync(outputDir)) {
|
||||||
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const wavFilePath = path.resolve(outputDir, `${itemId}.wav`);
|
||||||
|
const mp3FilePath = path.resolve(outputDir, `${itemId}.mp3`);
|
||||||
|
|
||||||
|
console.log(`WAVファイル保存開始: ${wavFilePath}`);
|
||||||
|
fs.writeFileSync(wavFilePath, audioBuffer);
|
||||||
|
console.log(`WAVファイル保存完了: ${wavFilePath}`);
|
||||||
|
|
||||||
|
console.log(`MP3変換開始: ${wavFilePath} -> ${mp3FilePath}`);
|
||||||
|
|
||||||
|
const ffmpegCmd = ffmpegPath || "ffmpeg";
|
||||||
|
const result = Bun.spawnSync({
|
||||||
|
cmd: [
|
||||||
|
ffmpegCmd,
|
||||||
|
"-i",
|
||||||
|
wavFilePath,
|
||||||
|
"-codec:a",
|
||||||
|
"libmp3lame",
|
||||||
|
"-qscale:a",
|
||||||
|
"2",
|
||||||
|
"-y", // Overwrite output file
|
||||||
|
mp3FilePath,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.exitCode !== 0) {
|
||||||
|
const stderr = result.stderr
|
||||||
|
? new TextDecoder().decode(result.stderr)
|
||||||
|
: "Unknown error";
|
||||||
|
throw new Error(`FFmpeg conversion failed: ${stderr}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wavファイルを削除
|
||||||
|
if (fs.existsSync(wavFilePath)) {
|
||||||
|
fs.unlinkSync(wavFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`TTS生成完了: ${itemId}`);
|
||||||
|
|
||||||
|
return path.basename(mp3FilePath);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user