mcpField
A
ASR

ASR

github.com/answerzhao创意与媒体26205 次安装41 周前更新
代码仓库

关于 ASR

基于 z-ai-web-dev-sdk 的语音转文字技能,面向需要在应用或脚本中处理音频转录的开发者。支持通过 CLI 快速转录,也可在后端代码中集成 SDK。

主要功能

  1. 01将音频转录为文本
  2. 02通过文件或 Base64 输入音频
  3. 03支持流式转录输出
  4. 04批量处理音频文件
  5. 05使用 SDK 构建后端转录流程

适用场景

  1. 01转录会议和访谈录音
  2. 02为播客生成文本稿
  3. 03将语音备忘录转换为文字
  4. 04分析客服通话内容

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/answerzhao/agent-skills --skill asr

安装后在 Claude Code 中输入/asr确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

LICENSE.txt
.DS_Store
scripts/
asr.ts

详细介绍

ASR(语音转文本)技能

本技能介绍如何使用 z-ai-web-dev-sdk 包实现语音转文本(ASR)功能,从而将语音音频准确转录为文本。

技能路径

技能位置:{project_path}/skills/ASR

此技能位于项目中的上述路径。

参考脚本:{Skill Location}/scripts/ 目录中提供了示例测试脚本,便于快速测试和参考。请参阅 {Skill Location}/scripts/asr.ts,其中包含可运行的示例。

概述

语音转文本(ASR,即自动语音识别)可让你构建能够将音频文件中的口语转换为书面文本的应用,从而实现语音控制界面、转录服务和音频内容分析。

重要提示:z-ai-web-dev-sdk 只能在后端代码中使用。切勿在客户端代码中使用。

前提条件

已安装 z-ai-web-dev-sdk 包。请按以下示例所示导入。

CLI 用法(适用于简单任务)

对于简单的音频转录任务,可以使用 z-ai CLI,而不必编写代码。它适用于快速转录、测试音频文件或批量处理。

从文件进行基本转录
# Transcribe an audio file
z-ai asr --file ./audio.wav

# Save transcription to JSON file
z-ai asr -f ./recording.mp3 -o transcript.json

# Transcribe and view output
z-ai asr --file ./interview.wav --output result.json
从 Base64 进行转录
# Transcribe from base64 encoded audio
z-ai asr --base64 "UklGRiQAAABXQVZFZm10..." -o result.json

# Using short option
z-ai asr -b "base64_encoded_audio_data" -o transcript.json
流式输出
# Stream transcription results
z-ai asr -f ./audio.wav --stream
CLI 参数
  • --file, -f <path>:必需(未使用 --base64 时)——音频文件路径
  • --base64, -b <base64>:必需(未使用 --file 时)——Base64 编码的音频
  • --output, -o <path>:可选——输出文件路径(JSON 格式)
  • --stream:可选——流式输出转录结果
支持的音频格式

ASR 服务支持多种音频格式,包括:

  • WAV(.wav)
  • MP3(.mp3)
  • 其他常见音频格式
何时使用 CLI,何时使用 SDK

适用于 CLI 的场景:

  • 快速转录音频文件
  • 测试语音识别准确度
  • 简单的批处理脚本
  • 一次性转录任务

适用于 SDK 的场景:

  • 在应用中实时转录音频
  • 与录音系统集成
  • 自定义音频处理工作流
  • 处理流式音频的生产应用

ASR 基本实现

简单音频转录
import ZAI from 'z-ai-web-dev-sdk';
import fs from 'fs';

async function transcribeAudio(audioFilePath) {
  const zai = await ZAI.create();

  // Read audio file and convert to base64
  const audioFile = fs.readFileSync(audioFilePath);
  const base64Audio = audioFile.toString('base64');

  const response = await zai.audio.asr.create({
    file_base64: base64Audio
  });

  return response.text;
}

// Usage
const transcription = await transcribeAudio('./audio.wav');
console.log('Transcription:', transcription);
转录多个音频文件
import ZAI from 'z-ai-web-dev-sdk';
import fs from 'fs';

async function transcribeBatch(audioFilePaths) {
  const zai = await ZAI.create();
  const results = [];

  for (const filePath of audioFilePaths) {
    try {
      const audioFile = fs.readFileSync(filePath);
      const base64Audio = audioFile.toString('base64');

      const response = await zai.audio.asr.create({
        file_base64: base64Audio
      });

      results.push({
        file: filePath,
        success: true,
        transcription: response.text
      });
    } catch (error) {
      results.push({
        file: filePath,
        success: false,
        error: error.message
      });
    }
  }

  return results;
}

// Usage
const files = ['./interview1.wav', './interview2.wav', './interview3.wav'];
const transcriptions = await transcribeBatch(files);

transcriptions.forEach(result => {
  if (result.success) {
    console.log(`${result.file}: ${result.transcription}`);
  } else {
    console.error(`${result.file}: Error - ${result.error}`);
  }
});

高级用例

带元数据的音频文件处理
import ZAI from 'z-ai-web-dev-sdk';
import fs from 'fs';
import path from 'path';

async function transcribeWithMetadata(audioFilePath) {
  const zai = await ZAI.create();

  // Get file metadata
  const stats = fs.statSync(audioFilePath);
  const audioFile = fs.readFileSync(audioFilePath);
  const base64Audio = audioFile.toString('base64');

  const startTime = Date.now();

  const response = await zai.audio.asr.create({
    file_base64: base64Audio
  });

  const endTime = Date.now();

  return {
    filename: path.basename(audioFilePath),
    filepath: audioFilePath,
    fileSize: stats.size,
    transcription: response.text,
    wordCount: response.text.split(/\s+/).length,
    processingTime: endTime - startTime,
    timestamp: new Date().toISOString()
  };
}

// Usage
const result = await transcribeWithMetadata('./meeting_recording.wav');
console.log('Transcription Details:', JSON.stringify(result, null, 2));
实时音频处理服务
import ZAI from 'z-ai-web-dev-sdk';
import fs from 'fs';

class ASRService {
  constructor() {
    this.zai = null;
    this.transcriptionCache = new Map();
  }

  async initialize() {
    this.zai = await ZAI.create();
  }

  generateCacheKey(audioBuffer) {
    const crypto = require('crypto');
    return crypto.createHash('md5').update(audioBuffer).digest('hex');
  }

  async transcribe(audioFilePath, useCache = true) {
    const audioBuffer = fs.readFileSync(audioFilePath);
    const cacheKey = this.generateCacheKey(audioBuffer);

    // Check cache
    if (useCache && this.transcriptionCache.has(cacheKey)) {
      return {
        transcription: this.transcriptionCache.get(cacheKey),
        cached: true
      };
    }

    // Transcribe audio
    const base64Audio = audioBuffer.toString('base64');

    const response = await this.zai.audio.asr.create({
      file_base64: base64Audio
    });

    // Cache result
    if (useCache) {
      this.transcriptionCache.set(cacheKey, response.text);
    }

    return {
      transcription: response.text,
      cached: false
    };
  }

  clearCache() {
    this.transcriptionCache.clear();
  }

  getCacheSize() {
    return this.transcriptionCache.size;
  }
}

// Usage
const asrService = new ASRService();
await asrService.initialize();

const result1 = await asrService.transcribe('./audio.wav');
console.log('First call (not cached):', result1);

const result2 = await asrService.transcribe('./audio.wav');
console.log('Second call (cached):', result2);
转录目录中的文件
import ZAI from 'z-ai-web-dev-sdk';
import fs from 'fs';
import path from 'path';

async function transcribeDirectory(directoryPath, outputJsonPath) {
  const zai = await ZAI.create();

  // Get all audio files
  const files = fs.readdirSync(directoryPath);
  const audioFiles = files.filter(file => 
    /\.(wav|mp3|m4a|flac|ogg)$/i.test(file)
  );

  const results = {
    directory: directoryPath,
    totalFiles: audioFiles.length,
    processedAt: new Date().toISOString(),
    transcriptions: []
  };

  for (const filename of audioFiles) {
    const filePath = path.join(directoryPath, filename);

    try {
      const audioFile = fs.readFileSync(filePath);
      const base64Audio = audioFile.toString('base64');

      const response = await zai.audio.asr.create({
        file_base64: base64Audio
      });

      results.transcriptions.push({
        filename: filename,
        success: true,
        text: response.text,
        wordCount: response.text.split(/\s+/).length
      });

      console.log(`✓ Transcribed: ${filename}`);
    } catch (error) {
      results.transcriptions.push({
        filename: filename,
        success: false,
        error: error.message
      });

      console.error(`✗ Failed: ${filename} - ${error.message}`);
    }
  }

  // Save results to JSON
  fs.writeFileSync(
    outputJsonPath,
    JSON.stringify(results, null, 2)
  );

  return results;
}

// Usage
const results = await transcribeDirectory(
  './audio-recordings',
  './transcriptions.json'
);

console.log(`\nProcessed ${results.totalFiles} files`);
console.log(`Successful: ${results.transcriptions.filter(t => t.success).length}`);
console.log(`Failed: ${results.transcriptions.filter(t => !t.success).length}`);

最佳实践

1. 音频格式处理
import ZAI from 'z-ai-web-dev-sdk';
import fs from 'fs';

async function transcribeAnyFormat(audioFilePath) {
  // Supported formats: WAV, MP3, M4A, FLAC, OGG, etc.
  const validExtensions = ['.wav', '.mp3', '.m4a', '.flac', '.ogg'];
  const ext = audioFilePath.toLowerCase().substring(audioFilePath.lastIndexOf('.'));

  if (!validExtensions.includes(ext)) {
    throw new Error(`Unsupported audio format: ${ext}`);
  }

  const zai = await ZAI.create();
  const audioFile = fs.readFileSync(audioFilePath);
  const base64Audio = audioFile.toString('base64');

  const response = await zai.audio.asr.create({
    file_base64: base64Audio
  });

  return response.text;
}
2. 错误处理
import ZAI from 'z-ai-web-dev-sdk';
import fs from 'fs';

async function safeTranscribe(audioFilePath) {
  try {
    // Validate file exists
    if (!fs.existsSync(audioFilePath)) {
      throw new Error(`File not found: ${audioFilePath}`);
    }

    // Check file size (e.g., limit to 100MB)
    const stats = fs.statSync(audioFilePath);
    const fileSizeMB = stats.size / (1024 * 1024);
    
    if (fileSizeMB > 100) {
      throw new Error(`File too large: ${fileSizeMB.toFixed(2)}MB (max 100MB)`);
    }

    // Transcribe
    const zai = await ZAI.create();
    const audioFile = fs.readFileSync(audioFilePath);
    const base64Audio = audioFile.toString('base64');

    const response = await zai.audio.asr.create({
      file_base64: base64Audio
    });

    if (!response.text || response.text.trim().length === 0) {
      throw new Error('Empty transcription result');
    }

    return {
      success: true,
      transcription: response.text,
      filePath: audioFilePath,
      fileSize: stats.size
    };
  } catch (error) {
    console.error('Transcription error:', error);
    return {
      success: false,
      error: error.message,
      filePath: audioFilePath
    };
  }
}
3. 转录结果后处理
function cleanTranscription(text) {
  // Remove excessive whitespace
  text = text.replace(/\s+/g, ' ').trim();

  // Capitalize first letter of sentences
  text = text.replace(/(^\w|[.!?]\s+\w)/g, match => match.toUpperCase());

  // Remove filler words (optional)
  const fillers = ['um', 'uh', 'ah', 'like', 'you know'];
  const fillerPattern = new RegExp(`\\b(${fillers.join('|')})\\b`, 'gi');
  text = text.replace(fillerPattern, '').replace(/\s+/g, ' ');

  return text;
}

async function transcribeAndClean(audioFilePath) {
  const zai = await ZAI.create();
  
  const audioFile = fs.readFileSync(audioFilePath);
  const base64Audio = audioFile.toString('base64');

  const response = await zai.audio.asr.create({
    file_base64: base64Audio
  });

  return {
    raw: response.text,
    cleaned: cleanTranscription(response.text)
  };
}

常见用例

  1. 会议转录:将录制的会议转换为可搜索文本
  2. 访谈处理:转录访谈内容,以便分析和存档
  3. 播客转录:为播客节目创建文本版本
  4. 语音备忘录:将语音备忘录转换为文本,以便查阅
  5. 呼叫中心分析:分析客户服务通话
  6. 无障碍支持:为音频内容提供文本替代形式
  7. 语音命令:实现语音控制应用
  8. 语言学习:转录发音练习内容

集成示例

Express.js API 端点
import express from 'express';
import multer from 'multer';
import ZAI from 'z-ai-web-dev-sdk';
import fs from 'fs';

const app = express();
const upload = multer({ dest: 'uploads/' });

let zaiInstance;

async function initZAI() {
  zaiInstance = await ZAI.create();
}

app.post('/api/transcribe', upload.single('audio'), async (req, res) => {
  try {
    if (!req.file) {
      return res.status(400).json({ error: 'No audio file provided' });
    }

    const audioFile = fs.readFileSync(req.file.path);
    const base64Audio = audioFile.toString('base64');

    const response = await zaiInstance.audio.asr.create({
      file_base64: base64Audio
    });

    // Clean up uploaded file
    fs.unlinkSync(req.file.path);

    res.json({
      success: true,
      transcription: response.text,
      wordCount: response.text.split(/\s+/).length
    });
  } catch (error) {
    // Clean up on error
    if (req.file && fs.existsSync(req.file.path)) {
      fs.unlinkSync(req.file.path);
    }

    res.status(500).json({
      success: false,
      error: error.message
    });
  }
});

initZAI().then(() => {
  app.listen(3000, () => {
    console.log('ASR API running on port 3000');
  });
});

故障排除

问题:“SDK must be used in backend”

  • 解决方案:确保 z-ai-web-dev-sdk 仅在服务端代码中导入

问题:转录结果为空或不正确

  • 解决方案:检查音频质量和格式,并确认音频中包含清晰的语音

问题:处理大文件失败

  • 解决方案:考虑将大型音频文件拆分为较小的片段

问题:转录速度慢

  • 解决方案:为重复转录实现缓存,并优化文件大小

问题:处理大文件时出现内存错误

  • 解决方案:分块处理文件,或增加 Node.js 内存限制

性能提示

  1. 复用 SDK 实例:创建一次,重复使用
  2. 实施缓存:缓存重复文件的转录结果
  3. 批量处理:通过适当的队列,高效处理多个文件
  4. 音频优化:尽可能在处理前压缩音频文件
  5. 异步操作:适当时使用 Promise.all 并行处理

音频质量指南

为获得最佳转录效果:

  • 采样率:16kHz 或更高
  • 格式:推荐使用 WAV、MP3 或 M4A
  • 噪声水平:尽量减少背景噪声
  • 语音清晰度:发音清晰,语速正常
  • 文件大小:建议单个文件小于 100MB

请记住

  • 仅在后端代码中使用 z-ai-web-dev-sdk
  • SDK 已安装,按示例所示导入即可
  • 处理前必须将音频文件转换为 base64
  • 为生产应用实现适当的错误处理
  • 考虑音频质量,以获得最佳转录准确度
  • 处理后清理临时文件
  • 缓存经常转录的文件的结果

评论

支持 Markdown