UNPKG

@kajidog/mcp-tts-voicevox

Version:

VOICEVOX integration for MCP - Text-to-Speech server using VOICEVOX engine

213 lines (212 loc) 9.56 kB
"use strict"; 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);