@kajidog/mcp-tts-voicevox 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,81 +2,63 @@
2
2
 
3
3
  VOICEVOXを使用した音声合成MCPサーバー
4
4
 
5
+ ## 特徴
6
+
7
+ - **キュー管理機能** - 複数の音声合成リクエストを効率的に処理
8
+ - **プリフェッチ** - 次の音声を事前に生成し、再生をスムーズに
9
+
5
10
  ## 必要条件
6
11
 
7
12
  - Node.js
8
13
  - [VOICEVOXエンジン](https://voicevox.hiroshiba.jp/)
9
14
 
10
- ## 機能概要
11
-
12
- - テキストから音声を合成して再生
13
- - テキストから音声合成用クエリを生成
14
- - 音声合成用クエリから音声ファイルを生成
15
- - テキストまたはクエリを音声生成キューに追加
16
-
17
- ## 使い方
18
-
19
- ### インストール
15
+ ## インストール
20
16
 
21
17
  ```bash
22
18
  npm install -g @kajidog/mcp-tts-voicevox
23
19
  ```
24
20
 
25
- ### 実行
21
+ ## 使い方
22
+
23
+ ### MCPサーバーとして
26
24
 
27
25
  1. VOICEVOXエンジンを起動
28
- 2. 以下のコマンドを実行
26
+ 2. MCPサーバーを起動
29
27
 
30
28
  ```bash
31
29
  npx @kajidog/mcp-tts-voicevox
32
30
  ```
33
31
 
34
- ### MCPツールとして使用
32
+ ### ライブラリとして
35
33
 
36
- #### 1. テキストを音声に変換して再生
34
+ プロジェクトに直接インポートして使用することも可能です:
37
35
 
38
- ```typescript
39
- await mcp.invoke("speak", {
40
- text: "こんにちは!", // 読み上げるテキスト
41
- speaker: 1 // 話者ID(オプション)
42
- });
36
+ ```bash
37
+ npm install @kajidog/mcp-tts-voicevox
43
38
  ```
44
39
 
45
- #### 2. テキストから音声合成用クエリを生成
40
+ ```javascript
41
+ import { VoicevoxClient } from "@kajidog/mcp-tts-voicevox";
46
42
 
47
- ```typescript
48
- const queryResult = await mcp.invoke("generate_query", {
49
- text: "こんにちは!", // 音声合成するテキスト
50
- speaker: 1 // 話者ID(オプション)
43
+ // クライアントを初期化
44
+ const client = new VoicevoxClient({
45
+ url: "http://localhost:50021", // VOICEVOXエンジンのURL
46
+ defaultSpeaker: 1, // デフォルト話者ID(オプション)
47
+ defaultSpeedScale: 1.0 // デフォルト速度(オプション)
51
48
  });
52
49
 
53
- // 返されたテキストをJSONにパース
54
- const query = JSON.parse(queryResult.content[0].text);
55
- ```
56
-
57
- #### 3. 音声合成用クエリから音声ファイルを生成
50
+ // テキストを音声に変換して再生
51
+ await client.speak("こんにちは");
58
52
 
59
- ```typescript
60
- const fileResult = await mcp.invoke("synthesize_file", {
61
- query: query, // 音声合成用クエリ
62
- output: "/path/to/output.wav", // 出力ファイルパス
63
- speaker: 1 // 話者ID(オプション)
64
- });
65
-
66
- // 生成された音声ファイルのパス
67
- const filePath = fileResult.content[0].text;
53
+ // テキストから音声ファイルを生成
54
+ const filePath = await client.generateAudioFile("こんにちは", "./output.wav");
68
55
  ```
69
56
 
70
- ## 活用例
71
-
72
- 1. **テキストを直接音声に変換**
73
- - `speak` - 短いテキストをすぐに読み上げたいとき
74
-
75
- 2. **細かい音声設定をカスタマイズ**
76
- - `generate_query` → クエリ編集 → `synthesize_file` の流れで高度な調整が可能
57
+ ## 主な機能
77
58
 
78
- 3. **バッチ処理で大量の音声ファイルを作成**
79
- - テキストをクエリに変換してから一括で音声ファイルを生成
59
+ - **テキスト読み上げ** (`speak`) - テキストを音声に変換して再生
60
+ - **クエリ生成** (`generate_query`) - 音声合成用クエリの作成
61
+ - **ファイル生成** (`synthesize_file`) - クエリから音声ファイルを生成
80
62
 
81
63
  ## 環境変数
82
64
 
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ const types_1 = require("./voicevox/types");
10
10
  Object.defineProperty(exports, "VoicevoxError", { enumerable: true, get: function () { return types_1.VoicevoxError; } });
11
11
  const server = new mcp_js_1.McpServer({
12
12
  name: "MCP TTS Voicevox",
13
- version: "0.0.6",
13
+ version: "0.0.7",
14
14
  description: "A Voicevox server that converts text to speech for playback and saving.",
15
15
  });
16
16
  // VoicevoxClientを一度だけインスタンス化
@@ -16,7 +16,7 @@ class VoicevoxApi {
16
16
  */
17
17
  async generateQuery(text, speaker = 1) {
18
18
  try {
19
- const endpoint = `/audio_query?text=${encodeURIComponent(text)}&speaker=${speaker}`;
19
+ const endpoint = `/audio_query?text=${encodeURIComponent(text)}&speaker=${encodeURIComponent(speaker.toString())}`;
20
20
  const query = await this.makeRequest("post", endpoint, null, {
21
21
  "Content-Type": "application/json",
22
22
  });
@@ -31,7 +31,7 @@ class VoicevoxApi {
31
31
  */
32
32
  async synthesize(query, speaker = 1) {
33
33
  try {
34
- return await this.makeRequest("post", `/synthesis?speaker=${speaker}`, query, {
34
+ return await this.makeRequest("post", `/synthesis?speaker=${encodeURIComponent(speaker.toString())}`, query, {
35
35
  "Content-Type": "application/json",
36
36
  Accept: "audio/wav",
37
37
  }, "arraybuffer");
@@ -45,7 +45,7 @@ class VoicevoxApi {
45
45
  */
46
46
  async generateQueryFromPreset(text, presetId, coreVersion) {
47
47
  try {
48
- let endpoint = `/audio_query_from_preset?text=${encodeURIComponent(text)}&preset_id=${presetId}`;
48
+ let endpoint = `/audio_query_from_preset?text=${encodeURIComponent(text)}&preset_id=${encodeURIComponent(presetId.toString())}`;
49
49
  if (coreVersion) {
50
50
  endpoint += `&core_version=${encodeURIComponent(coreVersion)}`;
51
51
  }
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ErrorHandler = exports.VoicevoxError = exports.VoicevoxErrorCode = void 0;
3
4
  exports.handleError = handleError;
4
5
  exports.formatError = formatError;
5
6
  /**
@@ -24,3 +25,91 @@ function formatError(message, error) {
24
25
  console.error(`${message}: ${errorMsg}`, error);
25
26
  return `${message}: ${errorMsg}`;
26
27
  }
28
+ /**
29
+ * VOICEVOX関連のエラーコード
30
+ */
31
+ var VoicevoxErrorCode;
32
+ (function (VoicevoxErrorCode) {
33
+ VoicevoxErrorCode["API_CONNECTION_ERROR"] = "api_connection_error";
34
+ VoicevoxErrorCode["QUERY_GENERATION_ERROR"] = "query_generation_error";
35
+ VoicevoxErrorCode["SYNTHESIS_ERROR"] = "synthesis_error";
36
+ VoicevoxErrorCode["FILE_OPERATION_ERROR"] = "file_operation_error";
37
+ VoicevoxErrorCode["PLAYBACK_ERROR"] = "playback_error";
38
+ VoicevoxErrorCode["QUEUE_OPERATION_ERROR"] = "queue_operation_error";
39
+ VoicevoxErrorCode["UNKNOWN_ERROR"] = "unknown_error";
40
+ })(VoicevoxErrorCode || (exports.VoicevoxErrorCode = VoicevoxErrorCode = {}));
41
+ /**
42
+ * VOICEVOXエラークラス
43
+ */
44
+ class VoicevoxError extends Error {
45
+ constructor(message, code = VoicevoxErrorCode.UNKNOWN_ERROR, originalError) {
46
+ super(message);
47
+ this.name = "VoicevoxError";
48
+ this.code = code;
49
+ this.originalError = originalError;
50
+ }
51
+ /**
52
+ * エラー発生箇所とスタックトレースを含むエラーの詳細情報を取得
53
+ */
54
+ getDetailedMessage() {
55
+ let details = `${this.message} [${this.code}]`;
56
+ if (this.originalError instanceof Error) {
57
+ details += `\nOriginal Error: ${this.originalError.message}`;
58
+ if (this.originalError.stack) {
59
+ details += `\nStack: ${this.originalError.stack}`;
60
+ }
61
+ }
62
+ return details;
63
+ }
64
+ }
65
+ exports.VoicevoxError = VoicevoxError;
66
+ /**
67
+ * エラーハンドリングユーティリティクラス
68
+ * アプリケーション全体で統一されたエラーハンドリングを提供
69
+ */
70
+ class ErrorHandler {
71
+ /**
72
+ * エラーをVoicevoxError形式に変換して例外をスロー
73
+ */
74
+ static throw(message, code = VoicevoxErrorCode.UNKNOWN_ERROR, originalError) {
75
+ console.error(`[${code}] ${message}`, originalError);
76
+ throw new VoicevoxError(message, code, originalError);
77
+ }
78
+ /**
79
+ * APIエラーを処理
80
+ */
81
+ static handleApiError(message, error) {
82
+ return this.throw(message, VoicevoxErrorCode.API_CONNECTION_ERROR, error);
83
+ }
84
+ /**
85
+ * クエリ生成エラーを処理
86
+ */
87
+ static handleQueryGenerationError(message, error) {
88
+ return this.throw(message, VoicevoxErrorCode.QUERY_GENERATION_ERROR, error);
89
+ }
90
+ /**
91
+ * 音声合成エラーを処理
92
+ */
93
+ static handleSynthesisError(message, error) {
94
+ return this.throw(message, VoicevoxErrorCode.SYNTHESIS_ERROR, error);
95
+ }
96
+ /**
97
+ * ファイル操作エラーを処理
98
+ */
99
+ static handleFileError(message, error) {
100
+ return this.throw(message, VoicevoxErrorCode.FILE_OPERATION_ERROR, error);
101
+ }
102
+ /**
103
+ * 再生エラーを処理
104
+ */
105
+ static handlePlaybackError(message, error) {
106
+ return this.throw(message, VoicevoxErrorCode.PLAYBACK_ERROR, error);
107
+ }
108
+ /**
109
+ * キュー操作エラーを処理
110
+ */
111
+ static handleQueueError(message, error) {
112
+ return this.throw(message, VoicevoxErrorCode.QUEUE_OPERATION_ERROR, error);
113
+ }
114
+ }
115
+ exports.ErrorHandler = ErrorHandler;
@@ -18,12 +18,14 @@ exports.VoicevoxClient = void 0;
18
18
  const player_1 = require("./player");
19
19
  const utils_1 = require("./utils");
20
20
  const error_1 = require("./error");
21
+ const api_1 = require("./api");
21
22
  class VoicevoxClient {
22
23
  constructor(config) {
23
24
  this.validateConfig(config);
24
25
  this.defaultSpeaker = config.defaultSpeaker ?? 1;
25
26
  this.defaultSpeedScale = config.defaultSpeedScale ?? 1.0;
26
27
  this.maxSegmentLength = 150;
28
+ this.api = new api_1.VoicevoxApi(config.url);
27
29
  this.player = new player_1.VoicevoxPlayer(config.url);
28
30
  }
29
31
  /**
@@ -38,10 +40,30 @@ class VoicevoxClient {
38
40
  const speakerId = this.getSpeakerId(speaker);
39
41
  const speed = this.getSpeedScale(speedScale);
40
42
  const segments = (0, utils_1.splitText)(text, this.maxSegmentLength);
41
- for (const segment of segments) {
42
- const query = await this.player.generateQuery(segment, speakerId);
43
- query.speedScale = speed;
44
- await this.player.enqueueWithQuery(query, speakerId);
43
+ const queueManager = this.player.getQueueManager();
44
+ if (segments.length === 0) {
45
+ return "テキストが空です";
46
+ }
47
+ // 最初のセグメントを優先的に処理して再生を早く開始
48
+ if (segments.length > 0) {
49
+ const firstQuery = await this.generateQuery(segments[0], speakerId);
50
+ firstQuery.speedScale = speed;
51
+ await queueManager.enqueueQuery(firstQuery, speakerId);
52
+ }
53
+ // 残りのセグメントは非同期で処理
54
+ if (segments.length > 1) {
55
+ // 残りのセグメントを非同期で処理する関数
56
+ const processRemainingSegments = async () => {
57
+ for (let i = 1; i < segments.length; i++) {
58
+ const query = await this.generateQuery(segments[i], speakerId);
59
+ query.speedScale = speed;
60
+ await queueManager.enqueueQuery(query, speakerId);
61
+ }
62
+ };
63
+ // 非同期で開始するが結果を待たない
64
+ processRemainingSegments().catch((error) => {
65
+ console.error("残りのセグメント処理中にエラーが発生しました:", error);
66
+ });
45
67
  }
46
68
  return `音声生成キューに追加しました: ${text}`;
47
69
  }
@@ -59,7 +81,8 @@ class VoicevoxClient {
59
81
  async generateQuery(text, speaker, speedScale) {
60
82
  try {
61
83
  const speakerId = this.getSpeakerId(speaker);
62
- const query = await this.player.generateQuery(text, speakerId);
84
+ // 直接APIを使用してクエリを生成
85
+ const query = await this.api.generateQuery(text, speakerId);
63
86
  query.speedScale = this.getSpeedScale(speedScale);
64
87
  return query;
65
88
  }
@@ -79,14 +102,33 @@ class VoicevoxClient {
79
102
  try {
80
103
  const speakerId = this.getSpeakerId(speaker);
81
104
  const speed = this.getSpeedScale(speedScale);
105
+ // キューマネージャーにアクセス
106
+ const queueManager = this.player.getQueueManager();
82
107
  if (typeof textOrQuery === "string") {
108
+ // テキストからクエリを生成
83
109
  const query = await this.generateQuery(textOrQuery, speakerId);
84
110
  query.speedScale = speed;
85
- return await this.player.synthesizeToFile(query, outputPath, speakerId);
111
+ // 直接APIで音声合成
112
+ const audioData = await this.api.synthesize(query, speakerId);
113
+ // 一時ファイル保存またはパス指定の保存
114
+ if (!outputPath) {
115
+ return await queueManager.saveTempAudioFile(audioData);
116
+ }
117
+ else {
118
+ return await this.player.synthesizeToFile(query, outputPath, speakerId);
119
+ }
86
120
  }
87
121
  else {
122
+ // クエリを使って音声合成
88
123
  const query = { ...textOrQuery, speedScale: speed };
89
- return await this.player.synthesizeToFile(query, outputPath, speakerId);
124
+ const audioData = await this.api.synthesize(query, speakerId);
125
+ // 一時ファイル保存またはパス指定の保存
126
+ if (!outputPath) {
127
+ return await queueManager.saveTempAudioFile(audioData);
128
+ }
129
+ else {
130
+ return await this.player.synthesizeToFile(query, outputPath, speakerId);
131
+ }
90
132
  }
91
133
  }
92
134
  catch (error) {
@@ -98,12 +140,40 @@ class VoicevoxClient {
98
140
  try {
99
141
  const speakerId = this.getSpeakerId(speaker);
100
142
  const speed = this.getSpeedScale(speedScale);
143
+ const queueManager = this.player.getQueueManager();
101
144
  if (typeof textOrQuery === "string") {
102
- return await this.speak(textOrQuery, speakerId, speed);
145
+ // テキストの場合:分割して処理
146
+ const segments = (0, utils_1.splitText)(textOrQuery, this.maxSegmentLength);
147
+ if (segments.length === 0) {
148
+ return "テキストが空です";
149
+ }
150
+ // 最初のセグメントを優先処理
151
+ if (segments.length > 0) {
152
+ const firstQuery = await this.generateQuery(segments[0], speakerId);
153
+ firstQuery.speedScale = speed;
154
+ await queueManager.enqueueQuery(firstQuery, speakerId);
155
+ }
156
+ // 残りのセグメントは非同期で処理
157
+ if (segments.length > 1) {
158
+ // 非同期で処理する関数
159
+ const processRemainingSegments = async () => {
160
+ for (let i = 1; i < segments.length; i++) {
161
+ const query = await this.generateQuery(segments[i], speakerId);
162
+ query.speedScale = speed;
163
+ await queueManager.enqueueQuery(query, speakerId);
164
+ }
165
+ };
166
+ // 非同期で開始するが結果を待たない
167
+ processRemainingSegments().catch((error) => {
168
+ console.error("残りのセグメント処理中にエラーが発生しました:", error);
169
+ });
170
+ }
171
+ return `テキストをキューに追加しました: ${textOrQuery}`;
103
172
  }
104
173
  else {
174
+ // クエリをキューに追加
105
175
  const query = { ...textOrQuery, speedScale: speed };
106
- await this.player.enqueueWithQuery(query, speakerId);
176
+ await queueManager.enqueueQuery(query, speakerId);
107
177
  return "クエリをキューに追加しました";
108
178
  }
109
179
  }
@@ -1,47 +1,18 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
3
  exports.VoicevoxPlayer = void 0;
37
4
  const queue_1 = require("./queue");
38
5
  const error_1 = require("./error");
39
6
  const api_1 = require("./api");
40
- const fsPromises = __importStar(require("fs/promises"));
41
- const path_1 = require("path");
42
- const promises_1 = require("fs/promises");
43
- const uuid_1 = require("uuid");
44
- const os_1 = require("os");
7
+ /**
8
+ * エラーハンドラープロキシ
9
+ * メソッドをエラーハンドリングで包む高階関数
10
+ */
11
+ function withErrorHandling(method, errorMessage) {
12
+ return method().catch((error) => {
13
+ throw (0, error_1.handleError)(errorMessage, error);
14
+ });
15
+ }
45
16
  /**
46
17
  * VOICEVOX音声プレイヤークラス
47
18
  * キュー管理システムを使用して音声の合成と再生を行う
@@ -54,9 +25,9 @@ class VoicevoxPlayer {
54
25
  */
55
26
  constructor(voicevoxUrl = "http://localhost:50021", prefetchSize = 2) {
56
27
  // APIインスタンスを作成
57
- this.api = new api_1.VoicevoxApi(voicevoxUrl);
28
+ const api = new api_1.VoicevoxApi(voicevoxUrl);
58
29
  // キューマネージャーにAPIインスタンスを注入
59
- this.queueManager = new queue_1.VoicevoxQueueManager(this.api, prefetchSize);
30
+ this.queueManager = new queue_1.VoicevoxQueueManager(api, prefetchSize);
60
31
  // デフォルトで再生を開始
61
32
  this.queueManager.startPlayback();
62
33
  // エラーイベントのログ記録
@@ -72,12 +43,9 @@ class VoicevoxPlayer {
72
43
  * @param speaker 話者ID
73
44
  */
74
45
  async enqueue(text, speaker = 1) {
75
- try {
46
+ return withErrorHandling(async () => {
76
47
  await this.queueManager.enqueueText(text, speaker);
77
- }
78
- catch (error) {
79
- (0, error_1.handleError)("キューへの追加中にエラーが発生しました", error);
80
- }
48
+ }, "テキストのキュー追加中にエラーが発生しました");
81
49
  }
82
50
  /**
83
51
  * クエリを使ってキューに追加
@@ -85,127 +53,65 @@ class VoicevoxPlayer {
85
53
  * @param speaker 話者ID
86
54
  */
87
55
  async enqueueWithQuery(query, speaker = 1) {
88
- try {
56
+ return withErrorHandling(async () => {
89
57
  await this.queueManager.enqueueQuery(query, speaker);
90
- }
91
- catch (error) {
92
- (0, error_1.handleError)("クエリからの音声生成中にエラーが発生しました", error);
93
- }
58
+ }, "クエリのキュー追加中にエラーが発生しました");
94
59
  }
95
60
  /**
96
61
  * テキストから音声合成用クエリを生成
62
+ * キューマネージャーの内部機能を使用
97
63
  * @param text 合成するテキスト
98
64
  * @param speaker 話者ID
99
65
  */
100
66
  async generateQuery(text, speaker = 1) {
101
- try {
102
- // 一時的にキューにテキストを追加してクエリを取得
103
- const item = await this.queueManager.enqueueText(text, speaker);
104
- // クエリが生成されるまで待機
105
- const maxRetries = 40;
106
- const retryInterval = 500;
107
- let retryCount = 0;
108
- return await new Promise((resolve, reject) => {
109
- const checkQuery = () => {
110
- const status = this.queueManager.getItemStatus(item.id);
111
- const currentItem = this.queueManager
112
- .getQueue()
113
- .find((i) => i.id === item.id);
114
- if (currentItem?.query) {
115
- // クエリが取得できたらキューから削除して返す
116
- const query = { ...currentItem.query };
117
- this.queueManager.removeItem(item.id);
118
- resolve(query);
119
- return;
120
- }
121
- if (status === queue_1.QueueItemStatus.ERROR) {
122
- this.queueManager.removeItem(item.id);
123
- reject(new Error("クエリの生成に失敗しました"));
124
- return;
125
- }
126
- if (retryCount >= maxRetries) {
127
- this.queueManager.removeItem(item.id);
128
- reject(new Error("クエリの生成がタイムアウトしました"));
129
- return;
130
- }
131
- retryCount++;
132
- setTimeout(checkQuery, retryInterval);
133
- };
134
- checkQuery();
135
- });
136
- }
137
- catch (error) {
138
- throw (0, error_1.handleError)("音声クエリ生成中にエラーが発生しました", error);
139
- }
67
+ return withErrorHandling(async () => {
68
+ // AudioGeneratorのgenerateQueryを直接呼び出す
69
+ return await this.queueManager
70
+ .getAudioGenerator()
71
+ .generateQuery(text, speaker);
72
+ }, "音声合成クエリの生成中にエラーが発生しました");
140
73
  }
141
74
  /**
142
75
  * 音声合成用クエリから音声ファイルを生成
76
+ * 重複を避けるためにQueueManagerの機能に委譲
77
+ *
143
78
  * @param query 音声合成用クエリ
144
79
  * @param output 出力ファイルパスまたは出力ディレクトリ(省略時は一時ディレクトリに生成)
145
80
  * @param speaker 話者ID
146
81
  */
147
82
  async synthesizeToFile(query, output, speaker = 1) {
148
- try {
149
- // クエリから直接音声を合成(キューを使わない)
150
- const audioData = await this.api.synthesize(query, speaker);
151
- // outputが未定義または空文字列の場合は、一時ディレクトリにファイルを作成
83
+ return withErrorHandling(async () => {
84
+ // クエリから音声を合成
85
+ const api = this.queueManager.getApi();
86
+ const audioData = await api.synthesize(query, speaker);
87
+ // ファイル保存処理をFileManagerに完全に委譲
88
+ const fileManager = this.queueManager.getFileManager();
89
+ // 出力パスが指定されていない場合は一時ファイル、指定されている場合は指定パスに保存
152
90
  if (!output) {
153
- const tempFilePath = this.createTempFilePath();
154
- await fsPromises.writeFile(tempFilePath, Buffer.from(audioData));
155
- return tempFilePath;
91
+ return await fileManager.saveTempAudioFile(audioData);
156
92
  }
157
- // 出力が実際にディレクトリかファイルパスか判断
158
- let targetPath = output;
159
- let isDir = false;
160
- try {
161
- const outputStat = await (0, promises_1.stat)(output);
162
- isDir = outputStat.isDirectory();
93
+ else {
94
+ return await fileManager.saveAudioFile(audioData, output);
163
95
  }
164
- catch (err) {
165
- // ファイルまたはディレクトリが存在しない場合
166
- // 末尾がスラッシュで終わる場合はディレクトリと見なす
167
- isDir = output.endsWith("/") || output.endsWith("\\");
168
- }
169
- // ディレクトリの場合、ファイル名を生成
170
- if (isDir) {
171
- const filename = `voice-${(0, uuid_1.v4)()}.wav`;
172
- targetPath = (0, path_1.join)(output, filename);
173
- }
174
- // 出力ディレクトリが存在するか確認し、存在しない場合は作成
175
- await fsPromises.mkdir((0, path_1.dirname)(targetPath), { recursive: true });
176
- // 音声データを指定された出力先に書き込み
177
- await fsPromises.writeFile(targetPath, Buffer.from(audioData));
178
- return targetPath;
179
- }
180
- catch (error) {
181
- throw (0, error_1.handleError)("音声ファイル生成中にエラーが発生しました", error);
182
- }
96
+ }, "音声ファイル生成中にエラーが発生しました");
183
97
  }
184
98
  /**
185
- * 一時ファイルのパスを生成
186
- * @returns 一時ファイルのパス
99
+ * 再生を開始
187
100
  */
188
- createTempFilePath() {
189
- const uniqueFilename = `voicevox-${(0, uuid_1.v4)()}.wav`;
190
- return (0, path_1.join)((0, os_1.tmpdir)(), uniqueFilename);
191
- }
192
- /**
193
- * キューをクリア
194
- */
195
- clearQueue() {
196
- this.queueManager.clearQueue();
101
+ startPlayback() {
102
+ this.queueManager.startPlayback();
197
103
  }
198
104
  /**
199
105
  * 再生を一時停止
200
106
  */
201
107
  pausePlayback() {
202
- return this.queueManager.pausePlayback();
108
+ this.queueManager.pausePlayback();
203
109
  }
204
110
  /**
205
111
  * 再生を再開
206
112
  */
207
113
  resumePlayback() {
208
- return this.queueManager.resumePlayback();
114
+ this.queueManager.resumePlayback();
209
115
  }
210
116
  /**
211
117
  * キュー内のアイテム数を取得
@@ -214,17 +120,25 @@ class VoicevoxPlayer {
214
120
  return this.queueManager.getQueue().length;
215
121
  }
216
122
  /**
217
- * イベントリスナーを追加
218
- * キュー管理システムからのイベントを監視できるようにする
123
+ * キューが空かどうかを確認
124
+ */
125
+ isQueueEmpty() {
126
+ return this.queueManager.getQueue().length === 0;
127
+ }
128
+ /**
129
+ * キューが再生中かどうかを確認
219
130
  */
220
- addEventListener(event, listener) {
221
- this.queueManager.addEventListener(event, listener);
131
+ isPlaying() {
132
+ return this.queueManager
133
+ .getQueue()
134
+ .some((item) => item.status === queue_1.QueueItemStatus.PLAYING);
222
135
  }
223
136
  /**
224
- * イベントリスナーを削除
137
+ * キューマネージャーインスタンスを取得
138
+ * 高度な操作のため公開
225
139
  */
226
- removeEventListener(event, listener) {
227
- this.queueManager.removeEventListener(event, listener);
140
+ getQueueManager() {
141
+ return this.queueManager;
228
142
  }
229
143
  }
230
144
  exports.VoicevoxPlayer = VoicevoxPlayer;