@ww_nero/media 1.0.1 → 1.0.3

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/index.js CHANGED
@@ -11,7 +11,7 @@ const {
11
11
  } = require('@modelcontextprotocol/sdk/types.js');
12
12
 
13
13
  const ASR_API_KEY = process.env.ASR_API_KEY || '';
14
- const ASR_UPLOAD_URL = process.env.ASR_UPLOAD_URL || '';
14
+ const ASR_UPLOAD_URL = 'http://fsheep.com:10808/upload';
15
15
 
16
16
  const SUPPORTED_AUDIO_TYPES = ['.mp3', '.wav'];
17
17
  const MAX_AUDIO_DURATION_SECONDS = 60;
@@ -279,7 +279,26 @@ const runAsrScript = (audioPath, outputPath, uploadUrl, apiKey) => {
279
279
  if (code === 0) {
280
280
  resolve(stdout.trim());
281
281
  } else {
282
- const errorMsg = stderr.trim() || stdout.trim() || `进程退出码: ${code}`;
282
+ // 尝试解析结构化的 ASR 错误信息
283
+ const stderrContent = stderr.trim();
284
+ const asrErrorMatch = stderrContent.match(/ASR_ERROR:\s*(\{.*\})/);
285
+ if (asrErrorMatch) {
286
+ try {
287
+ const errorInfo = JSON.parse(asrErrorMatch[1]);
288
+ const parts = [errorInfo.error || '识别失败'];
289
+ if (errorInfo.audio_url) {
290
+ parts.push(`静态资源地址: ${errorInfo.audio_url}`);
291
+ }
292
+ if (errorInfo.details) {
293
+ parts.push(`服务器错误信息: ${errorInfo.details}`);
294
+ }
295
+ reject(new Error(parts.join('\n')));
296
+ return;
297
+ } catch {
298
+ // JSON 解析失败,使用原始错误信息
299
+ }
300
+ }
301
+ const errorMsg = stderrContent || stdout.trim() || `进程退出码: ${code}`;
283
302
  reject(new Error(errorMsg));
284
303
  }
285
304
  });
@@ -299,9 +318,6 @@ const asr = async ({ working_directory, audio_file }) => {
299
318
  if (!ASR_API_KEY) {
300
319
  throw new Error('请配置 ASR_API_KEY 环境变量');
301
320
  }
302
- if (!ASR_UPLOAD_URL) {
303
- throw new Error('请配置 ASR_UPLOAD_URL 环境变量(完整的上传接口 URL,如 http://server.domain.com/upload)');
304
- }
305
321
 
306
322
  const workingDir = resolveWorkingDirectory(working_directory);
307
323
  const audioPath = resolveAudioFile(workingDir, audio_file);
@@ -327,7 +343,7 @@ const asr = async ({ working_directory, audio_file }) => {
327
343
  const server = new Server(
328
344
  {
329
345
  name: 'media',
330
- version: '1.0.1',
346
+ version: '1.0.3',
331
347
  },
332
348
  {
333
349
  capabilities: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ww_nero/media",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "MCP server for media processing, including ASR speech recognition",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -88,22 +88,16 @@ def upload_audio(upload_url: str, audio_path: str) -> str:
88
88
  files = {'file': (audio_path.name, f)}
89
89
  response = requests.post(upload_url, files=files, timeout=60)
90
90
 
91
- if response.status_code == 409:
92
- # 文件已存在,从响应中获取文件名或根据原文件名推断
93
- data = response.json()
94
- if 'fileName' in data:
95
- return data['fileName']
96
- # 如果没有返回文件名,使用原始文件名的扩展名
97
- raise Exception(f"文件已存在: {data.get('message', '未知错误')}")
98
-
99
91
  if response.status_code != 200:
100
92
  raise Exception(f"上传失败: {response.status_code} - {response.text}")
101
93
 
102
94
  data = response.json()
103
- if 'fileName' not in data:
95
+
96
+ # 响应格式: {'success': True, 'data': {'path': '/tmp/xxx.wav'}}
97
+ if not data.get('success') or 'data' not in data or 'path' not in data['data']:
104
98
  raise Exception(f"上传响应格式错误: {data}")
105
99
 
106
- return data['fileName']
100
+ return Path(data['data']['path']).name
107
101
 
108
102
 
109
103
  def get_static_url(upload_url: str, filename: str) -> str:
@@ -125,6 +119,14 @@ def get_static_url(upload_url: str, filename: str) -> str:
125
119
  return f"{base_url.rstrip('/')}/{filename}"
126
120
 
127
121
 
122
+ class AsrError(Exception):
123
+ """ASR 识别错误,包含静态资源 URL 和错误详情"""
124
+ def __init__(self, message: str, audio_url: str = None, details: str = None):
125
+ self.audio_url = audio_url
126
+ self.details = details
127
+ super().__init__(message)
128
+
129
+
128
130
  def transcribe_audio(audio_url: str, api_key: str) -> list:
129
131
  """
130
132
  调用阿里云 ASR 接口进行语音识别
@@ -139,14 +141,17 @@ def transcribe_audio(audio_url: str, api_key: str) -> list:
139
141
  dashscope.api_key = api_key
140
142
 
141
143
  # 发起异步识别请求
142
- transcribe_response = Transcription.async_call(
143
- model='paraformer-v2',
144
- file_urls=[audio_url],
145
- language_hints=['zh', 'en', 'ja']
146
- )
144
+ try:
145
+ transcribe_response = Transcription.async_call(
146
+ model='paraformer-v2',
147
+ file_urls=[audio_url],
148
+ language_hints=['zh', 'en', 'ja']
149
+ )
150
+ except Exception as e:
151
+ raise AsrError(f"ASR 请求失败: {e}", audio_url=audio_url, details=str(e))
147
152
 
148
153
  if not transcribe_response or not hasattr(transcribe_response, 'output'):
149
- raise Exception("ASR 请求失败: 无效的响应")
154
+ raise AsrError("ASR 请求失败: 无效的响应", audio_url=audio_url)
150
155
 
151
156
  # 轮询等待识别完成
152
157
  while True:
@@ -157,10 +162,26 @@ def transcribe_audio(audio_url: str, api_key: str) -> list:
157
162
  transcribe_response = Transcription.fetch(task=transcribe_response.output.task_id)
158
163
 
159
164
  if transcribe_response.status_code != HTTPStatus.OK:
160
- raise Exception(f"ASR 识别失败: {transcribe_response.status_code}")
165
+ error_msg = getattr(transcribe_response, 'message', str(transcribe_response.status_code))
166
+ raise AsrError(
167
+ f"ASR 识别失败: {transcribe_response.status_code}",
168
+ audio_url=audio_url,
169
+ details=error_msg
170
+ )
161
171
 
162
172
  if transcribe_response.output.task_status == 'FAILED':
163
- raise Exception("ASR 识别任务失败")
173
+ # 尝试获取更详细的错误信息
174
+ error_details = None
175
+ results = transcribe_response.output.get('results', [])
176
+ for result in results:
177
+ if result.get('subtask_status') == 'FAILED':
178
+ error_details = result.get('message', '未知错误')
179
+ break
180
+ raise AsrError(
181
+ "ASR 识别任务失败",
182
+ audio_url=audio_url,
183
+ details=error_details
184
+ )
164
185
 
165
186
  # 获取识别结果
166
187
  results = transcribe_response.output.get('results', [])
@@ -196,6 +217,7 @@ def main():
196
217
  print("错误: 请通过 --api-key 参数或 ASR_API_KEY 环境变量提供 API Key", file=sys.stderr)
197
218
  sys.exit(1)
198
219
 
220
+ audio_url = None
199
221
  try:
200
222
  # 1. 上传音频文件
201
223
  print(f"正在上传音频文件: {args.audio}")
@@ -211,8 +233,7 @@ def main():
211
233
  transcriptions = transcribe_audio(audio_url, api_key)
212
234
 
213
235
  if not transcriptions:
214
- print("警告: 未获取到识别结果", file=sys.stderr)
215
- sys.exit(1)
236
+ raise AsrError("未获取到识别结果", audio_url=audio_url)
216
237
 
217
238
  # 4. 生成 SRT 文件
218
239
  subtitle_count = asr_to_srt(transcriptions, args.output)
@@ -222,8 +243,23 @@ def main():
222
243
  except FileNotFoundError as e:
223
244
  print(f"错误: {e}", file=sys.stderr)
224
245
  sys.exit(1)
246
+ except AsrError as e:
247
+ # 输出结构化的错误信息,包含静态资源 URL
248
+ error_info = {
249
+ 'error': str(e),
250
+ 'audio_url': e.audio_url or audio_url,
251
+ 'details': e.details
252
+ }
253
+ print(f"ASR_ERROR: {json.dumps(error_info, ensure_ascii=False)}", file=sys.stderr)
254
+ sys.exit(1)
225
255
  except Exception as e:
226
- print(f"错误: {e}", file=sys.stderr)
256
+ # 其他错误也尝试包含静态资源 URL
257
+ error_info = {
258
+ 'error': str(e),
259
+ 'audio_url': audio_url,
260
+ 'details': None
261
+ }
262
+ print(f"ASR_ERROR: {json.dumps(error_info, ensure_ascii=False)}", file=sys.stderr)
227
263
  sys.exit(1)
228
264
 
229
265