@kajidog/mcp-tts-voicevox
Version:
VOICEVOX integration for MCP - Text-to-Speech server using VOICEVOX engine
347 lines (346 loc) • 14.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const voicevox_1 = require("./voicevox");
const path_1 = require("path");
const os_1 = require("os");
const fs = __importStar(require("fs/promises"));
const soundPlay = require("sound-play");
// ----- ユーティリティ関数 -----
/**
* 音声ファイルを再生するユーティリティ関数
* @param filePath 再生する音声ファイルのパス
* @param description 再生内容の説明
*/
async function playAudioFile(filePath, description = "") {
try {
const displayText = description ? `${description} (${filePath})` : filePath;
console.log(`🔊 音声ファイル「${displayText}」の再生を開始します...`);
await soundPlay.play(filePath);
console.log(`✅ 音声ファイル「${displayText}」の再生が完了しました`);
}
catch (error) {
console.error(`❌ 音声再生中にエラーが発生しました: ${error}`);
throw error;
}
}
/**
* ヘッダーを出力する関数
* @param title セクションタイトル
*/
function printHeader(title) {
console.log(`\n${"=".repeat(80)}`);
console.log(`📌 ${title}`);
console.log(`${"=".repeat(80)}`);
}
/**
* サブセクションのヘッダーを出力する関数
* @param title セクションタイトル
*/
function printSubHeader(title) {
console.log(`\n${"- ".repeat(40)}`);
console.log(`🔹 ${title}`);
console.log(`${"- ".repeat(40)}`);
}
// ----- クライアントテスト関数 -----
/**
* VoicevoxClientの基本機能をテストする
*/
async function testTextToSpeech(client, speaker) {
printSubHeader("テキストから音声再生のテスト");
const testText = "これはテストです。VOICEVOXの機能を検証します。";
// 1. speak テスト - テキストからの音声再生
console.log("➡️ テキストから直接音声再生");
const speakResult = await client.speak(testText, speaker);
console.log("✅ 結果:", speakResult);
// 2. generateQuery テスト - テキストから音声合成用クエリを生成
console.log("\n➡️ テキストから音声合成用クエリ生成");
const query = await client.generateQuery(testText, speaker);
console.log("✅ クエリ生成結果 (一部):", JSON.stringify(query).substring(0, 100) + "...");
return query;
}
/**
* 音声ファイル生成機能をテストする
*/
async function testAudioFileGeneration(client, query, speaker) {
printSubHeader("音声ファイル生成のテスト");
// 1. クエリから音声ファイルを生成
console.log("➡️ クエリから音声ファイル生成");
const outputPath = (0, path_1.join)((0, os_1.tmpdir)(), `voicevox-${Date.now()}.wav`);
const filePath = await client.generateAudioFile(query, outputPath, speaker);
console.log(`✅ 音声ファイル生成: ${filePath}`);
// ファイルが存在するか確認
const fileExists = await fs
.stat(filePath)
.then(() => true)
.catch(() => false);
console.log(`📂 ファイルの存在確認: ${fileExists ? "✅ 存在します" : "❌ 存在しません"}`);
// 生成した音声ファイルを再生
if (fileExists) {
await playAudioFile(filePath, "クエリから生成した音声");
}
// 2. テキストから直接音声ファイルを生成
console.log("\n➡️ テキストから直接音声ファイル生成");
const directFilePath = await client.generateAudioFile("直接ファイルに変換するテスト。", undefined, speaker);
console.log(`✅ 直接音声ファイル生成: ${directFilePath}`);
await playAudioFile(directFilePath, "テキストから直接生成した音声");
}
/**
* 再生速度変更機能をテストする
*/
async function testSpeedScale(client, speaker) {
printSubHeader("再生速度の変更テスト");
// 1. 速い再生速度のテスト
console.log("➡️ 再生速度を1.5倍に設定したテスト");
const speedTestFilePath = await client.generateAudioFile("これは再生速度を1.5倍に設定したテストです。", (0, path_1.join)((0, os_1.tmpdir)(), `voicevox-speed-${Date.now()}.wav`), speaker, 1.5 // 速度を1.5倍に設定
);
console.log(`✅ 再生速度1.5倍: ${speedTestFilePath}`);
await playAudioFile(speedTestFilePath, "速度を1.5倍に設定した音声");
// 遅い再生速度のテストはタイムアウトを起こしやすいため、条件付きでスキップ
const doSlowTest = process.env.TEST_SLOW_SPEED === "true";
if (doSlowTest) {
// 2. 遅い再生速度のテスト
console.log("\n➡️ 再生速度を0.8倍に設定したテスト");
try {
const slowSpeedTestFilePath = await client.generateAudioFile("これは再生速度を0.8倍に設定したテストです。ゆっくり話します。", (0, path_1.join)((0, os_1.tmpdir)(), `voicevox-slow-${Date.now()}.wav`), speaker, 0.8 // 速度を0.8倍に設定
);
console.log(`✅ 再生速度0.8倍: ${slowSpeedTestFilePath}`);
await playAudioFile(slowSpeedTestFilePath, "速度を0.8倍に設定した音声");
}
catch (error) {
console.warn(`⚠️ 遅い再生速度のテストはスキップされました: ${error}`);
}
}
else {
console.log("\n⏭️ 再生速度0.8倍のテストはスキップします (タイムアウト防止のため)");
}
}
/**
* 音声生成キュー機能をテストする
*/
async function testAudioQueue(client, query, speaker) {
printSubHeader("音声生成キューテスト");
// クエリを使って音声生成キューに追加
console.log("➡️ クエリを使って音声生成キューへの追加");
const enqueueResult = await client.enqueueAudioGeneration(query, speaker);
console.log(`✅ キュー追加結果:`, enqueueResult);
}
/**
* VoicevoxClientの基本機能をテストする統合関数
*/
async function testClient() {
try {
printHeader("VoicevoxClient直接テスト");
const client = new voicevox_1.VoicevoxClient({
url: "http://localhost:50021",
defaultSpeaker: 1,
defaultSpeedScale: 1.0,
});
// テスト用の話者
const speaker = 5; // 四国めたん (ノーマル)
// 1. 基本的なテキスト読み上げとクエリ生成
const query = await testTextToSpeech(client, speaker);
// 2. 音声ファイル生成
await testAudioFileGeneration(client, query, speaker);
// 3. 再生速度変更
await testSpeedScale(client, speaker);
// 4. 音声生成キュー
await testAudioQueue(client, query, speaker);
return query; // 後のテストで使用するためにクエリを返す
}
catch (error) {
console.error("❌ クライアントテスト中にエラーが発生しました:", error);
throw error;
}
}
// ----- MCPツール関数 -----
/**
* speak MCPツールをテストする
*/
async function testSpeakTool(client) {
printSubHeader("speak ツールのテスト");
// speak ツールハンドラ
const speakHandler = async (args) => {
try {
const { text, speaker, speedScale } = args;
console.log(`➡️ テキスト「${text}」を話者${speaker}、速度${speedScale || 1.0}で発話`);
const result = await client.speak(text, speaker, speedScale);
console.log("✅ speak 結果:", result);
return {
content: [{ type: "text", text: result }],
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error("❌ エラーが発生しました:", error);
return {
content: [{ type: "text", text: `エラー: ${errorMessage}` }],
};
}
};
// 通常のテスト
await speakHandler({ text: "MCPツールからのテスト発話です。", speaker: 1 });
// 速度を変更したテスト
await speakHandler({
text: "MCPツールから速度を1.3倍に設定したテスト発話です。",
speaker: 1,
speedScale: 1.3,
});
}
/**
* generate_query MCPツールをテストする
*/
async function testGenerateQueryTool(client) {
printSubHeader("generate_query ツールのテスト");
// generate_query ツールハンドラ
const generateQueryHandler = async (args) => {
try {
const { text, speaker, speedScale } = args;
console.log(`➡️ テキスト「${text}」を話者${speaker}、速度${speedScale || 1.0}でクエリ生成`);
const generatedQuery = await client.generateQuery(text, speaker, speedScale);
const queryJson = JSON.stringify(generatedQuery);
console.log("✅ クエリ生成結果 (一部):", queryJson.substring(0, 100) + "...");
return {
content: [{ type: "text", text: queryJson }],
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error("❌ エラーが発生しました:", error);
return {
content: [{ type: "text", text: `エラー: ${errorMessage}` }],
};
}
};
// 実行
const queryResponse = await generateQueryHandler({
text: "MCPツールからのクエリ生成テスト。",
speaker: 1,
speedScale: 1.2,
});
// テキストからJSONに変換
return JSON.parse(queryResponse.content[0].text);
}
/**
* synthesize_file MCPツールをテストする
*/
async function testSynthesizeFileTool(client, query) {
printSubHeader("synthesize_file ツールのテスト");
// synthesize_file ツールハンドラ
const synthesizeFileHandler = async (args) => {
try {
const { query: testQuery, output, speaker, speedScale } = args;
console.log(`➡️ クエリから音声ファイルを生成: 出力パス=${output}`);
const filePath = await client.generateAudioFile(testQuery, output, speaker, speedScale);
console.log("✅ ファイル生成結果:", filePath);
return {
content: [{ type: "text", text: filePath }],
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error("❌ エラーが発生しました:", error);
return {
content: [{ type: "text", text: `エラー: ${errorMessage}` }],
};
}
};
// 実行
const testOutputPath = "";
const fileResponse = await synthesizeFileHandler({
query: query,
output: testOutputPath,
speaker: 1,
speedScale: 0.9,
});
return fileResponse.content[0].text;
}
/**
* MCPツールをテストする統合関数
*/
async function testMcpTools(query) {
try {
printHeader("MCPツールテスト");
// VoicevoxClientを初期化
const client = new voicevox_1.VoicevoxClient({
url: process.env.VOICEVOX_URL ?? "http://localhost:50021",
defaultSpeaker: 1,
defaultSpeedScale: Number(process.env.VOICEVOX_DEFAULT_SPEED_SCALE || "1.0"),
});
// 1. speak ツールのテスト
await testSpeakTool(client);
// 2. generate_query ツールのテスト
const generatedQuery = await testGenerateQueryTool(client);
// 3. synthesize_file ツールのテスト
const filePath = await testSynthesizeFileTool(client, generatedQuery);
// 生成したファイルを再生
printSubHeader("生成したファイルの再生テスト");
console.log(`➡️ ファイル ${filePath} を再生します...`);
await playAudioFile(filePath, "MCPツールで生成した音声ファイル");
console.log("✅ ファイルの再生が完了しました");
// 待機してキューの処理が完了するのを待つ
console.log("\n⏳ MCP音声再生を待機しています...");
await new Promise((resolve) => setTimeout(resolve, 3000));
}
catch (error) {
console.error("❌ MCPツールテスト中にエラーが発生しました:", error);
throw error;
}
}
// ----- メイン実行関数 -----
/**
* メイン実行関数
*/
async function main() {
try {
console.log("🚀 VOICEVOXテストを開始します...");
// クライアントテスト実行
const query = await testClient();
// MCPツールテスト実行
await testMcpTools(query);
console.log("\n🎉 すべてのテストが完了しました!");
}
catch (error) {
console.error("❌ テスト中にエラーが発生しました:", error);
process.exit(1);
}
finally {
// 強制的にプロセスを終了
console.log("👋 プロセスを終了します...");
setTimeout(() => process.exit(0), 1000);
}
}
// プログラム実行
main();