@kajidog/mcp-tts-voicevox
Version:
VOICEVOX integration for MCP - Text-to-Speech server using VOICEVOX engine
213 lines (212 loc) • 9.56 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 __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.VoicevoxClient = void 0;
const player_1 = require("./player");
const utils_1 = require("./utils");
const error_1 = require("./error");
const api_1 = require("./api");
class VoicevoxClient {
constructor(config) {
this.validateConfig(config);
this.defaultSpeaker = config.defaultSpeaker ?? 1;
this.defaultSpeedScale = config.defaultSpeedScale ?? 1.0;
this.maxSegmentLength = 150;
this.api = new api_1.VoicevoxApi(config.url);
this.player = new player_1.VoicevoxPlayer(config.url);
}
/**
* テキストを音声に変換して再生します
* @param text 変換するテキスト
* @param speaker 話者ID(オプション)
* @param speedScale 再生速度(オプション)
* @returns 処理結果のメッセージ
*/
async speak(text, speaker, speedScale) {
try {
const speakerId = this.getSpeakerId(speaker);
const speed = this.getSpeedScale(speedScale);
const segments = (0, utils_1.splitText)(text, this.maxSegmentLength);
const queueManager = this.player.getQueueManager();
if (segments.length === 0) {
return "テキストが空です";
}
// 最初のセグメントを優先的に処理して再生を早く開始
if (segments.length > 0) {
const firstQuery = await this.generateQuery(segments[0], speakerId);
firstQuery.speedScale = speed;
await queueManager.enqueueQuery(firstQuery, speakerId);
}
// 残りのセグメントは非同期で処理
if (segments.length > 1) {
// 残りのセグメントを非同期で処理する関数
const processRemainingSegments = async () => {
for (let i = 1; i < segments.length; i++) {
const query = await this.generateQuery(segments[i], speakerId);
query.speedScale = speed;
await queueManager.enqueueQuery(query, speakerId);
}
};
// 非同期で開始するが結果を待たない
processRemainingSegments().catch((error) => {
console.error("残りのセグメント処理中にエラーが発生しました:", error);
});
}
return `音声生成キューに追加しました: ${text}`;
}
catch (error) {
return (0, error_1.formatError)("音声生成中にエラーが発生しました", error);
}
}
/**
* テキストから音声合成用クエリを生成します
* @param text 変換するテキスト
* @param speaker 話者ID(オプション)
* @param speedScale 再生速度(オプション)
* @returns 音声合成用クエリ
*/
async generateQuery(text, speaker, speedScale) {
try {
const speakerId = this.getSpeakerId(speaker);
// 直接APIを使用してクエリを生成
const query = await this.api.generateQuery(text, speakerId);
query.speedScale = this.getSpeedScale(speedScale);
return query;
}
catch (error) {
throw (0, error_1.handleError)("クエリ生成中にエラーが発生しました", error);
}
}
/**
* テキストから直接音声ファイルを生成します
* @param textOrQuery テキストまたは音声合成用クエリ
* @param outputPath 出力ファイルパス(オプション、省略時は一時ファイル)
* @param speaker 話者ID(オプション)
* @param speedScale 再生速度(オプション)
* @returns 生成した音声ファイルのパス
*/
async generateAudioFile(textOrQuery, outputPath, speaker, speedScale) {
try {
const speakerId = this.getSpeakerId(speaker);
const speed = this.getSpeedScale(speedScale);
// キューマネージャーにアクセス
const queueManager = this.player.getQueueManager();
if (typeof textOrQuery === "string") {
// テキストからクエリを生成
const query = await this.generateQuery(textOrQuery, speakerId);
query.speedScale = speed;
// 直接APIで音声合成
const audioData = await this.api.synthesize(query, speakerId);
// 一時ファイル保存またはパス指定の保存
if (!outputPath) {
return await queueManager.saveTempAudioFile(audioData);
}
else {
return await this.player.synthesizeToFile(query, outputPath, speakerId);
}
}
else {
// クエリを使って音声合成
const query = { ...textOrQuery, speedScale: speed };
const audioData = await this.api.synthesize(query, speakerId);
// 一時ファイル保存またはパス指定の保存
if (!outputPath) {
return await queueManager.saveTempAudioFile(audioData);
}
else {
return await this.player.synthesizeToFile(query, outputPath, speakerId);
}
}
}
catch (error) {
throw (0, error_1.handleError)("音声ファイル生成中にエラーが発生しました", error);
}
}
// 実装
async enqueueAudioGeneration(textOrQuery, speaker, speedScale) {
try {
const speakerId = this.getSpeakerId(speaker);
const speed = this.getSpeedScale(speedScale);
const queueManager = this.player.getQueueManager();
if (typeof textOrQuery === "string") {
// テキストの場合:分割して処理
const segments = (0, utils_1.splitText)(textOrQuery, this.maxSegmentLength);
if (segments.length === 0) {
return "テキストが空です";
}
// 最初のセグメントを優先処理
if (segments.length > 0) {
const firstQuery = await this.generateQuery(segments[0], speakerId);
firstQuery.speedScale = speed;
await queueManager.enqueueQuery(firstQuery, speakerId);
}
// 残りのセグメントは非同期で処理
if (segments.length > 1) {
// 非同期で処理する関数
const processRemainingSegments = async () => {
for (let i = 1; i < segments.length; i++) {
const query = await this.generateQuery(segments[i], speakerId);
query.speedScale = speed;
await queueManager.enqueueQuery(query, speakerId);
}
};
// 非同期で開始するが結果を待たない
processRemainingSegments().catch((error) => {
console.error("残りのセグメント処理中にエラーが発生しました:", error);
});
}
return `テキストをキューに追加しました: ${textOrQuery}`;
}
else {
// クエリをキューに追加
const query = { ...textOrQuery, speedScale: speed };
await queueManager.enqueueQuery(query, speakerId);
return "クエリをキューに追加しました";
}
}
catch (error) {
return (0, error_1.formatError)("音声生成中にエラーが発生しました", error);
}
}
/**
* 話者IDを取得(指定がない場合はデフォルト値を使用)
* @private
*/
getSpeakerId(speaker) {
return speaker ?? this.defaultSpeaker;
}
/**
* 再生速度を取得(指定がない場合はデフォルト値を使用)
* @private
*/
getSpeedScale(speedScale) {
return speedScale ?? this.defaultSpeedScale;
}
validateConfig(config) {
if (!config.url) {
throw new Error("VOICEVOXのURLが指定されていません");
}
try {
new URL(config.url);
}
catch {
throw new Error("無効なVOICEVOXのURLです");
}
}
}
exports.VoicevoxClient = VoicevoxClient;
// 型定義の再エクスポート
__exportStar(require("./types"), exports);