univoice 0.4.0 → 0.5.0
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 +64 -20
- package/dist/{base-Bll64DMp.d.ts → base-BsWrB3Ae.d.ts} +3 -0
- package/dist/{chunk-LVWPQ7DG.js → chunk-EHSTFTRI.js} +52 -3
- package/dist/chunk-EHSTFTRI.js.map +1 -0
- package/dist/{chunk-XEYV5Z5P.js → chunk-IDAZ5B4H.js} +520 -7
- package/dist/chunk-IDAZ5B4H.js.map +1 -0
- package/dist/{chunk-TFXIXMXO.js → chunk-T5AXVN4X.js} +155 -3
- package/dist/chunk-T5AXVN4X.js.map +1 -0
- package/dist/qwen-DUJgbnDy.d.ts +74 -0
- package/dist/qwen-FowCg-bd.d.ts +52 -0
- package/dist/{save-OBmee_PS.d.ts → save-BrWMA1TW.d.ts} +1 -1
- package/dist/src/asr/index.d.ts +3 -3
- package/dist/src/asr/index.js +1 -1
- package/dist/src/asr/providers/index.d.ts +26 -46
- package/dist/src/asr/providers/index.js +281 -3
- package/dist/src/asr/providers/index.js.map +1 -1
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.js +3 -3
- package/dist/src/tts/index.d.ts +2 -2
- package/dist/src/tts/index.js +2 -2
- package/dist/src/tts/providers/index.d.ts +6 -45
- package/dist/src/tts/providers/index.js +1 -1
- package/dist/{tee-3KInEaCJ.d.ts → tee-B8vAU0KE.d.ts} +3 -1
- package/examples/.env.example +3 -0
- package/examples/glm-asr-demo.ts +56 -0
- package/examples/glm-asr-stream-demo.ts +86 -0
- package/examples/glm-tts-demo.ts +62 -0
- package/examples/glm-tts-stream-demo.ts +65 -0
- package/examples/qwen-asr-demo.ts +56 -0
- package/examples/qwen-asr-stream-demo.ts +88 -0
- package/examples/qwen-asr-stream-input-demo.ts +207 -0
- package/package.json +4 -3
- package/dist/chunk-LVWPQ7DG.js.map +0 -1
- package/dist/chunk-TFXIXMXO.js.map +0 -1
- package/dist/chunk-XEYV5Z5P.js.map +0 -1
package/README.md
CHANGED
|
@@ -341,25 +341,40 @@ const asr = createASR({
|
|
|
341
341
|
|
|
342
342
|
## 支持的提供商
|
|
343
343
|
|
|
344
|
-
###
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
|
351
|
-
|
|
352
|
-
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
|
362
|
-
|
|
344
|
+
### 能力矩阵
|
|
345
|
+
|
|
346
|
+
各提供商对输入输出模式的支持情况如下,帮助您根据实际场景选择合适的提供商。
|
|
347
|
+
|
|
348
|
+
#### ASR 能力矩阵
|
|
349
|
+
|
|
350
|
+
| 提供商 | 标识符 | 流式输入 | 一次性输入 | 流式输出 | 一次性输出 |
|
|
351
|
+
|--------|--------|----------|------------|----------|----------|
|
|
352
|
+
| 豆包(火山引擎) | `doubao` | ✅ | ✅ | ✅ | ✅ |
|
|
353
|
+
| 通义千问 | `qwen` | ✅ | ✅ | ✅ | ✅ |
|
|
354
|
+
| 智谱 GLM | `glm` | ❌ | ✅ | ✅ | ✅ |
|
|
355
|
+
| OpenAI | `openai` | 待实现 | 待实现 | 待实现 | 待实现 |
|
|
356
|
+
| MiniMax | `minimax` | 待实现 | 待实现 | 待实现 | 待实现 |
|
|
357
|
+
| Gemini | `gemini` | 待实现 | 待实现 | 待实现 | 待实现 |
|
|
358
|
+
|
|
359
|
+
#### TTS 能力矩阵
|
|
360
|
+
|
|
361
|
+
| 提供商 | 标识符 | 流式输入 | 一次性输入 | 流式输出 | 一次性输出 |
|
|
362
|
+
|--------|--------|----------|------------|----------|----------|
|
|
363
|
+
| 豆包(火山引擎) | `doubao` | ✅ | ✅ | ✅ | ✅ |
|
|
364
|
+
| 通义千问 | `qwen` | ✅ | ✅ | ✅ | ✅ |
|
|
365
|
+
| 智谱 GLM | `glm` | ❌ | ✅ | ✅ | ✅ |
|
|
366
|
+
| OpenAI | `openai` | 待实现 | 待实现 | 待实现 | 待实现 |
|
|
367
|
+
| MiniMax | `minimax` | 待实现 | 待实现 | 待实现 | 待实现 |
|
|
368
|
+
| Gemini | `gemini` | 待实现 | 待实现 | 待实现 | 待实现 |
|
|
369
|
+
|
|
370
|
+
#### 能力说明
|
|
371
|
+
|
|
372
|
+
| 能力 | 说明 |
|
|
373
|
+
|------|------|
|
|
374
|
+
| **流式输入** | 支持边发边收,如 LLM 流式输出直接转语音、实时音频流识别 |
|
|
375
|
+
| **一次性输入** | 一次性发送完整文本/音频 |
|
|
376
|
+
| **流式输出** | 结果以流的形式返回,适合实时处理场景 |
|
|
377
|
+
| **一次性输出** | 返回完整结果,适合批量处理场景 |
|
|
363
378
|
|
|
364
379
|
### 配置示例
|
|
365
380
|
|
|
@@ -418,6 +433,14 @@ const tts = createTTS({
|
|
|
418
433
|
voice: 'longxiaochun_v3',
|
|
419
434
|
format: 'mp3',
|
|
420
435
|
});
|
|
436
|
+
|
|
437
|
+
const asr = createASR({
|
|
438
|
+
provider: 'qwen',
|
|
439
|
+
apiKey: process.env.QWEN_API_KEY,
|
|
440
|
+
model: 'paraformer-realtime-v2',
|
|
441
|
+
language: 'zh-CN',
|
|
442
|
+
format: 'mp3',
|
|
443
|
+
});
|
|
421
444
|
```
|
|
422
445
|
|
|
423
446
|
#### Gemini
|
|
@@ -431,6 +454,26 @@ const tts = createTTS({
|
|
|
431
454
|
});
|
|
432
455
|
```
|
|
433
456
|
|
|
457
|
+
#### 智谱 GLM
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
const tts = createTTS({
|
|
461
|
+
provider: 'glm',
|
|
462
|
+
apiKey: process.env.GLM_API_KEY,
|
|
463
|
+
model: 'glm-tts',
|
|
464
|
+
voice: 'tongtong', // 可选: xiaochen, chuichui, jam, kazi, douji, luodo, female, male
|
|
465
|
+
format: 'pcm', // 支持 wav 和 pcm,流式只支持 pcm
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
const asr = createASR({
|
|
469
|
+
provider: 'glm',
|
|
470
|
+
apiKey: process.env.GLM_API_KEY,
|
|
471
|
+
model: 'glm-asr-2512',
|
|
472
|
+
hotwords: ['人工智能', '机器学习'], // 可选:热词列表,提高特定词汇识别准确率
|
|
473
|
+
context: '这是一段技术演讲', // 可选:上下文文本,用于长文本场景优化
|
|
474
|
+
});
|
|
475
|
+
```
|
|
476
|
+
|
|
434
477
|
---
|
|
435
478
|
|
|
436
479
|
## 开发指南
|
|
@@ -545,4 +588,5 @@ src/
|
|
|
545
588
|
- [OpenAI](https://openai.com/)
|
|
546
589
|
- [MiniMax](https://www.minimaxi.com/)
|
|
547
590
|
- [阿里云通义千问](https://tongyi.aliyun.com/)
|
|
548
|
-
- [Google Gemini](https://ai.google.dev/)
|
|
591
|
+
- [Google Gemini](https://ai.google.dev/)
|
|
592
|
+
- [智谱 AI](https://open.bigmodel.cn/)
|
|
@@ -2,6 +2,7 @@ import { __name } from './chunk-7QVYU63E.js';
|
|
|
2
2
|
import { Buffer as Buffer$1 } from 'buffer';
|
|
3
3
|
import { spawn } from 'child_process';
|
|
4
4
|
import { writeFile } from 'fs/promises';
|
|
5
|
+
import path from 'path';
|
|
5
6
|
|
|
6
7
|
async function collectAudio(response, options = {}) {
|
|
7
8
|
const { audio } = response;
|
|
@@ -98,7 +99,39 @@ function concatUint8Arrays2(arrays) {
|
|
|
98
99
|
return result;
|
|
99
100
|
}
|
|
100
101
|
__name(concatUint8Arrays2, "concatUint8Arrays");
|
|
101
|
-
|
|
102
|
+
function createWavHeader(dataLength, sampleRate = 24e3, channels = 1, bitsPerSample = 16) {
|
|
103
|
+
const headerLength = 44;
|
|
104
|
+
const header = new Uint8Array(headerLength);
|
|
105
|
+
const view = new DataView(header.buffer);
|
|
106
|
+
view.setUint8(0, 82);
|
|
107
|
+
view.setUint8(1, 73);
|
|
108
|
+
view.setUint8(2, 70);
|
|
109
|
+
view.setUint8(3, 70);
|
|
110
|
+
view.setUint32(4, 36 + dataLength, true);
|
|
111
|
+
view.setUint8(8, 87);
|
|
112
|
+
view.setUint8(9, 65);
|
|
113
|
+
view.setUint8(10, 86);
|
|
114
|
+
view.setUint8(11, 69);
|
|
115
|
+
view.setUint8(12, 102);
|
|
116
|
+
view.setUint8(13, 109);
|
|
117
|
+
view.setUint8(14, 116);
|
|
118
|
+
view.setUint8(15, 32);
|
|
119
|
+
view.setUint32(16, 16, true);
|
|
120
|
+
view.setUint16(20, 1, true);
|
|
121
|
+
view.setUint16(22, channels, true);
|
|
122
|
+
view.setUint32(24, sampleRate, true);
|
|
123
|
+
view.setUint32(28, sampleRate * channels * (bitsPerSample / 8), true);
|
|
124
|
+
view.setUint16(32, channels * (bitsPerSample / 8), true);
|
|
125
|
+
view.setUint16(34, bitsPerSample, true);
|
|
126
|
+
view.setUint8(36, 100);
|
|
127
|
+
view.setUint8(37, 97);
|
|
128
|
+
view.setUint8(38, 116);
|
|
129
|
+
view.setUint8(39, 97);
|
|
130
|
+
view.setUint32(40, dataLength, true);
|
|
131
|
+
return header;
|
|
132
|
+
}
|
|
133
|
+
__name(createWavHeader, "createWavHeader");
|
|
134
|
+
async function saveAudio(filePath, source, options) {
|
|
102
135
|
const chunks = [];
|
|
103
136
|
if (isAsyncIterable(source)) {
|
|
104
137
|
for await (const chunk of source) {
|
|
@@ -112,6 +145,22 @@ async function saveAudio(filePath, source) {
|
|
|
112
145
|
chunks.push(...source);
|
|
113
146
|
}
|
|
114
147
|
const audio = concatUint8Arrays2(chunks);
|
|
148
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
149
|
+
if (ext === ".wav") {
|
|
150
|
+
const hasWavHeader = audio.length >= 4 && audio[0] === 82 && // 'R'
|
|
151
|
+
audio[1] === 73 && // 'I'
|
|
152
|
+
audio[2] === 70 && // 'F'
|
|
153
|
+
audio[3] === 70;
|
|
154
|
+
if (!hasWavHeader) {
|
|
155
|
+
const sampleRate = options?.sampleRate ?? 24e3;
|
|
156
|
+
const wavHeader = createWavHeader(audio.length, sampleRate);
|
|
157
|
+
const wavData = new Uint8Array(wavHeader.length + audio.length);
|
|
158
|
+
wavData.set(wavHeader);
|
|
159
|
+
wavData.set(audio, wavHeader.length);
|
|
160
|
+
await writeFile(filePath, wavData);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
115
164
|
await writeFile(filePath, audio);
|
|
116
165
|
}
|
|
117
166
|
__name(saveAudio, "saveAudio");
|
|
@@ -130,5 +179,5 @@ async function teeAudio(response, options = {}) {
|
|
|
130
179
|
__name(teeAudio, "teeAudio");
|
|
131
180
|
|
|
132
181
|
export { collectAudio, playAudio, saveAudio, saveTTSResponse, teeAudio };
|
|
133
|
-
//# sourceMappingURL=chunk-
|
|
134
|
-
//# sourceMappingURL=chunk-
|
|
182
|
+
//# sourceMappingURL=chunk-EHSTFTRI.js.map
|
|
183
|
+
//# sourceMappingURL=chunk-EHSTFTRI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tts/utils/collect.ts","../src/tts/utils/play.ts","../src/tts/utils/save.ts","../src/tts/utils/save-audio.ts","../src/tts/utils/tee.ts"],"names":["Buffer","concatUint8Arrays","writeFile"],"mappings":"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","file":"chunk-EHSTFTRI.js","sourcesContent":["import { Buffer } from 'node:buffer';\nimport type { TTSResponse } from '@/types/tts';\n\nexport interface CollectOptions {\n onChunk?: (chunk: Uint8Array) => void;\n onComplete?: (audio: Uint8Array) => void;\n onError?: (error: Error) => void;\n}\n\nexport async function collectAudio(\n response: TTSResponse,\n options: CollectOptions = {}\n): Promise<Uint8Array> {\n const { audio } = response;\n const chunks: Uint8Array[] = [];\n\n if (isUint8Array(audio)) {\n chunks.push(audio);\n } else if (isBuffer(audio)) {\n chunks.push(new Uint8Array(audio));\n }\n\n const result = concatUint8Arrays(chunks);\n\n if (options.onComplete) {\n options.onComplete(result);\n }\n\n return result;\n}\n\nfunction isUint8Array(value: unknown): value is Uint8Array {\n return value instanceof Uint8Array;\n}\n\nfunction isBuffer(value: unknown): value is Buffer {\n return Buffer.isBuffer(value);\n}\n\nfunction concatUint8Arrays(arrays: Uint8Array[]): Uint8Array {\n const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const arr of arrays) {\n result.set(arr, offset);\n offset += arr.length;\n }\n return result;\n}\n","import { spawn } from 'node:child_process';\nimport type { TTSResponse } from '@/types/tts';\n\nexport interface PlayOptions {\n player?: string;\n}\n\nexport async function playAudio(response: TTSResponse, options: PlayOptions = {}): Promise<void> {\n const player = options.player || 'afplay';\n\n let buffer: Buffer;\n if (response.audio instanceof Buffer) {\n buffer = response.audio;\n } else if (response.audio instanceof Uint8Array) {\n buffer = Buffer.from(response.audio);\n } else {\n throw new Error('Invalid audio data');\n }\n\n return new Promise((resolve, reject) => {\n const proc = spawn(player, [], { stdio: ['pipe', 'inherit', 'inherit'] });\n proc.stdin.write(buffer);\n proc.stdin.end();\n proc.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`Player exited with code ${code}`));\n }\n });\n proc.on('error', reject);\n });\n}\n","import { writeFile } from 'node:fs/promises';\nimport type { TTSResponse } from '@/types/tts';\n\nexport interface SaveOptions {\n filename?: string;\n directory?: string;\n}\n\n/**\n * 保存 TTSResponse 到文件\n * 自动生成文件名,适合快速保存 TTS 响应\n *\n * @param response TTS 响应对象\n * @param options 保存选项\n * @returns 保存的文件路径\n */\nexport async function saveTTSResponse(\n response: TTSResponse,\n options: SaveOptions = {}\n): Promise<string> {\n const { format } = response;\n const timestamp = Date.now();\n const filename = options.filename || `tts_${timestamp}.${format}`;\n const filepath = options.directory ? `${options.directory}/${filename}` : filename;\n\n let buffer: Buffer;\n if (response.audio instanceof Buffer) {\n buffer = response.audio;\n } else if (response.audio instanceof Uint8Array) {\n buffer = Buffer.from(response.audio);\n } else {\n throw new Error('Invalid audio data');\n }\n\n await writeFile(filepath, buffer);\n return filepath;\n}\n","import { writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport type { TTSStreamChunk } from '@/types/tts';\n\n/**\n * 判断是否为异步迭代器\n */\nfunction isAsyncIterable(value: unknown): value is AsyncIterable<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n Symbol.asyncIterator in value &&\n typeof (value as AsyncIterable<unknown>)[Symbol.asyncIterator] === 'function'\n );\n}\n\n/**\n * 判断是否为 TTSStreamChunk 类型\n */\nfunction isTTSStreamChunk(value: unknown): value is TTSStreamChunk {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'audioChunk' in value &&\n (value as TTSStreamChunk).audioChunk instanceof Uint8Array\n );\n}\n\n/**\n * 合并多个 Uint8Array\n */\nfunction concatUint8Arrays(arrays: Uint8Array[]): Uint8Array {\n const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const arr of arrays) {\n result.set(arr, offset);\n offset += arr.length;\n }\n return result;\n}\n\n/**\n * 创建 WAV 文件头\n * @param dataLength 音频数据长度\n * @param sampleRate 采样率(默认 24000)\n * @param channels 声道数(默认 1)\n * @param bitsPerSample 位深(默认 16)\n */\nfunction createWavHeader(\n dataLength: number,\n sampleRate = 24000,\n channels = 1,\n bitsPerSample = 16\n): Uint8Array {\n const headerLength = 44;\n const header = new Uint8Array(headerLength);\n const view = new DataView(header.buffer);\n\n // RIFF chunk descriptor\n view.setUint8(0, 0x52); // 'R'\n view.setUint8(1, 0x49); // 'I'\n view.setUint8(2, 0x46); // 'F'\n view.setUint8(3, 0x46); // 'F'\n view.setUint32(4, 36 + dataLength, true); // file size - 8\n view.setUint8(8, 0x57); // 'W'\n view.setUint8(9, 0x41); // 'A'\n view.setUint8(10, 0x56); // 'V'\n view.setUint8(11, 0x45); // 'E'\n\n // fmt sub-chunk\n view.setUint8(12, 0x66); // 'f'\n view.setUint8(13, 0x6d); // 'm'\n view.setUint8(14, 0x74); // 't'\n view.setUint8(15, 0x20); // ' '\n view.setUint32(16, 16, true); // sub-chunk size\n view.setUint16(20, 1, true); // audio format (PCM)\n view.setUint16(22, channels, true); // number of channels\n view.setUint32(24, sampleRate, true); // sample rate\n view.setUint32(28, sampleRate * channels * (bitsPerSample / 8), true); // byte rate\n view.setUint16(32, channels * (bitsPerSample / 8), true); // block align\n view.setUint16(34, bitsPerSample, true); // bits per sample\n\n // data sub-chunk\n view.setUint8(36, 0x64); // 'd'\n view.setUint8(37, 0x61); // 'a'\n view.setUint8(38, 0x74); // 't'\n view.setUint8(39, 0x61); // 'a'\n view.setUint32(40, dataLength, true); // data size\n\n return header;\n}\n\n/**\n * 保存音频数据到文件\n * 支持三种调用方式:\n * 1. saveAudio(filePath, chunks) - chunks 是 Uint8Array[]\n * 2. saveAudio(filePath, asyncIterable) - AsyncIterable<Uint8Array>\n * 3. saveAudio(filePath, asyncIterable) - AsyncIterable<TTSStreamChunk>\n *\n * @param filePath 目标文件路径\n * @param source 音频数据源,可以是 Uint8Array 数组或异步迭代器\n * @param options 可选参数\n * @param options.sampleRate 采样率,用于生成 WAV 头(默认 24000)\n */\nexport async function saveAudio(\n filePath: string,\n source: Uint8Array[] | AsyncIterable<Uint8Array> | AsyncIterable<TTSStreamChunk>,\n options?: { sampleRate?: number }\n): Promise<void> {\n const chunks: Uint8Array[] = [];\n\n // 判断是否为异步迭代器\n if (isAsyncIterable(source)) {\n for await (const chunk of source) {\n // 自动检测并提取 audioChunk\n if (isTTSStreamChunk(chunk)) {\n chunks.push(chunk.audioChunk);\n } else if (chunk instanceof Uint8Array) {\n chunks.push(chunk);\n }\n }\n } else {\n chunks.push(...source);\n }\n\n // 合并音频数据\n const audio = concatUint8Arrays(chunks);\n\n // 根据文件扩展名判断是否需要添加 WAV 头\n const ext = path.extname(filePath).toLowerCase();\n if (ext === '.wav') {\n // 检查是否已有 WAV 头(RIFF 标识)\n const hasWavHeader =\n audio.length >= 4 &&\n audio[0] === 0x52 && // 'R'\n audio[1] === 0x49 && // 'I'\n audio[2] === 0x46 && // 'F'\n audio[3] === 0x46; // 'F'\n\n if (!hasWavHeader) {\n // 添加 WAV 头\n const sampleRate = options?.sampleRate ?? 24000;\n const wavHeader = createWavHeader(audio.length, sampleRate);\n const wavData = new Uint8Array(wavHeader.length + audio.length);\n wavData.set(wavHeader);\n wavData.set(audio, wavHeader.length);\n await writeFile(filePath, wavData);\n return;\n }\n }\n\n await writeFile(filePath, audio);\n}\n","import { collectAudio } from '@/tts/utils/collect';\nimport { playAudio } from '@/tts/utils/play';\nimport { saveTTSResponse } from '@/tts/utils/save';\nimport type { TTSResponse } from '@/types/tts';\n\nexport interface TeeOptions {\n save?: {\n filename?: string;\n directory?: string;\n };\n play?: {\n player?: string;\n };\n}\n\nexport async function teeAudio(\n response: TTSResponse,\n options: TeeOptions = {}\n): Promise<TTSResponse> {\n const audio = await collectAudio(response);\n\n if (options.save) {\n await saveTTSResponse({ ...response, audio }, options.save);\n }\n\n if (options.play) {\n await playAudio({ ...response, audio }, options.play);\n }\n\n return { ...response, audio };\n}\n"]}
|