边缘 TTS
边缘 TTS 是一个强大的Text-to-Speech(TTS)软件包,它能利用微软的边缘能力. 此软件包允许您从文本中合成语音,并通过命令行界面(CLI)轻松管理语音选项.
以三种语文提供
用于 Edge 文本对语音服务的同一库, 在 Node.js, PHP 和 Go 中:
| 语言 | 安装 | 仓库 |
|---|---|---|
| 节点和浏览器 (这个) | npm install @andresaya/edge-tts | 断层/断层 |
| 前一頁 | composer require afaya/edge-tts | 复诊/对接-ts-php |
| 走开 | go get github.com/andresayac/edge-tts-go | 断层/断层 |
特征
- 文字对语言: 使用微软Edge的TTS能力将文本转换为自然语音.
- 类型脚本支持:包含有全面类型定义的完整类型脚本支持.
- 音频格式: MP3在48或96 kbit/s,和WebM/Opus.
- 多个声音: 访问各种声音以适应您的项目需求.
- 语音过滤:按语言和性别筛选声音,以更好地选择.
- 音频信息: 获取生成音频的详细信息(大小,持续时间,格式).
- 音频导出选项:以不同格式(raw,base64,或直接到文件)导出合成的音频.
- 流程支持: 实时流出音频数据,以提高性能.
- 词句边界:获得有精确时间戳的文字和句子时间.
- 字幕:生成 SRT 或 WebVTT 字幕,每句一个提示.
- 同时使用数字 :
generate()返回自己的结果,因此一个实例可以同时运行多个合成. - 可配置:在不等待发布的情况下,覆盖结束点,标题,超时和语音缓存.
- 命令线接口:使用简单的CLI来方便地访问功能.
- 简单整合: 模块结构可以方便地纳入现有项目.
安装
您可以通过 npm 或 bun 安装 Edge TTS :
bun add @andresaya/edge-tts
npm install @andresaya/edge-tts
在Node.js 18,20和24以及Bun上测试.
类型脚本支持
边缘 TTS 以 类型脚本 并包括完整类型定义。 无额外 @types 需要软件包.
可用类型
import {
EdgeTTS,
EdgeTTSError,
Constants,
Voice,
SynthesisOptions,
SynthesisResult,
EdgeTTSOptions,
WordBoundary,
SentenceBoundary
} from '@andresaya/edge-tts';
// Voice interface
interface Voice {
Name: string;
ShortName: string;
Gender: string; // 'Male' | 'Female'
Locale: string;
FriendlyName: string;
LocalName: string;
DisplayName?: string;
LocaleName?: string;
VoiceType?: string;
SecondaryLocaleList?: string[];
VoiceTag?: { TailoredScenarios: string[]; VoicePersonalities: string[] };
}
// Synthesis options
interface SynthesisOptions {
pitch?: string | number; // e.g., '+20Hz' or 20
rate?: string | number; // e.g., '50%' or 50
volume?: string | number; // e.g., '90%' or 90
outputFormat?: string; // e.g., Constants.OUTPUT_FORMAT.AUDIO_24KHZ_96KBITRATE_MONO_MP3
signal?: AbortSignal; // cancels the synthesis
timeout?: number; // inactivity timeout in ms for this call
}
// Word and sentence boundary metadata (offset and duration in 100 ns ticks)
interface WordBoundary {
type: "WordBoundary";
offset: number;
duration: number;
text: string;
}
interface SentenceBoundary {
type: "SentenceBoundary";
offset: number;
duration: number;
text: string;
}
// Returned by generate()
interface SynthesisResult {
audio: Uint8Array; // a Buffer in Node.js
format: string; // file extension: 'mp3' or 'webm'
outputFormat: string;
wordBoundaries: WordBoundary[];
sentenceBoundaries: SentenceBoundary[];
duration: number; // estimated, in seconds
}
类型安全使用示例
import { EdgeTTS, SynthesisOptions, Constants } from '@andresaya/edge-tts';
const tts = new EdgeTTS();
const options: SynthesisOptions = {
pitch: '+10Hz',
rate: '100%',
volume: '90%',
outputFormat: Constants.OUTPUT_FORMAT.WEBM_24KHZ_16BIT_MONO_OPUS
};
await tts.synthesize("TypeScript example", 'en-US-AriaNeural', options);
const info = tts.getAudioInfo(); // Returns: { size: number; format: string; estimatedDuration: number }
const boundaries = tts.getWordBoundaries(); // Returns: WordBoundary[]
使用量
命令线接口
在全球安装以使用 CLI :
npm install -g @andresaya/edge-tts
从文本中合成语音 :
edge-tts synthesize -t "Hello, world!" -o hello_world_audio
其他选项:
# Choose the voice, read the text from a file, write subtitles too
edge-tts synthesize -f story.txt -v es-CO-SalomeNeural -o story.mp3 --subtitles story.srt
# Read from stdin, WebM/Opus output, WebVTT subtitles
cat story.txt | edge-tts synthesize -f - --format webm-24khz-16bit-mono-opus -o story --subtitles story.vtt
要列出可用的声音, 可选择过滤 :
edge-tts voice-list
edge-tts voice-list --locale es-CO --gender female
融入您的项目
import { EdgeTTS } from '@andresaya/edge-tts';
// Initialize the EdgeTTS service
const tts = new EdgeTTS();
配置
每个选项都是可选的。 默认来自 Constants,所以当微软改变服务时,您可以将库指向新的值自己,而不是等待发布.
const tts = new EdgeTTS({
wssUrl: 'wss://api.msedgeservices.com/tts/cognitiveservices/websocket/v1',
voicesUrl: 'https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list',
secMsGecVersion: '1-155.0.4283.45',
headers: { 'User-Agent': '...' }, // Node.js only, added to the default headers
timeout: 30000, // ms without data before giving up
voicesCacheTtl: 600000, // ms to reuse the voice list, 0 to always fetch it
});
如果计算机的时钟关闭,服务会拒绝连接. 在Node.js中,库然后从服务器读取时间并重复一次.
API 参考
语音管理
获取全部声音
const voices = await tts.getVoices();
console.log(`Found ${voices.length} voices`);
按语言过滤声音
// Get all English voices
const englishVoices = await tts.getVoicesByLanguage('en');
// Get specific locale voices
const usEnglishVoices = await tts.getVoicesByLanguage('en-US');
按性别过滤声音
// Get all female voices
const femaleVoices = await tts.getVoicesByGender('Female');
// Get all male voices
const maleVoices = await tts.getVoicesByGender('Male');
文本合成
基本综述
// Simple synthesis with default voice
await tts.synthesize("Hello, world!");
// Synthesis with specific voice
await tts.synthesize("Hello, world!", 'en-US-AriaNeural');
将语音匹配到文本
声音是特定的地方。 一个声音只能读取 : 中文、西里尔文、阿拉伯文或德瓦纳加里文 英语语音返回完全没有音频 :
// Nothing to play: an English voice cannot read this.
await tts.synthesize("这是一个测试", 'en-US-AriaNeural');
// Works.
await tts.synthesize("这是一个测试", 'zh-CN-XiaoxiaoNeural');
英文句子中的几个外语是好的
仅当其他脚本是文本的大部分时才会出现. toBuffer() 丢入
而不是交出一个空缓冲器.
带有选项的高级合成
await tts.synthesize("Hello, world!", 'en-US-AriaNeural', {
rate: '50%', // Speech rate: -100% to +200% (or number)
volume: '90%', // Speech volume: -100% to +100% (or number)
pitch: '+20Hz', // Voice pitch: -100Hz to +100Hz (or number)
outputFormat: 'audio-24khz-96kbitrate-mono-mp3' // Audio output format
});
有生成的独立结果( )
synthesize() 将音频保存在实例上,因此在一次实例上同时两次通话互相覆盖. generate() 返回所有结果, 并留下一个实例 :
const [hello, bye] = await Promise.all([
tts.generate("Hello!", 'en-US-AriaNeural'),
tts.generate("Goodbye!", 'en-US-AriaNeural'),
]);
hello.audio; // Buffer in Node.js, Uint8Array in the browser
hello.format; // 'mp3'
hello.duration; // estimated seconds
hello.wordBoundaries; // WordBoundary[]
hello.sentenceBoundaries; // SentenceBoundary[]
音频输出格式
您可以使用 outputFormat 选项 :
import { EdgeTTS, Constants } from '@andresaya/edge-tts';
const tts = new EdgeTTS();
// High quality MP3
await tts.synthesize("Hello!", 'en-US-AriaNeural', {
outputFormat: Constants.OUTPUT_FORMAT.AUDIO_24KHZ_96KBITRATE_MONO_MP3
});
await tts.toFile('./output/audio'); // Automatically saved as .mp3
// WebM/Opus for web
await tts.synthesize("Hello!", 'en-US-AriaNeural', {
outputFormat: Constants.OUTPUT_FORMAT.WEBM_24KHZ_16BIT_MONO_OPUS
});
await tts.toFile('./output/audio'); // Automatically saved as .webm
可用格式 :
| 常数 | 数值 |
|---|---|
AUDIO_24KHZ_48KBITRATE_MONO_MP3 (默认) | audio-24khz-48kbitrate-mono-mp3 |
AUDIO_24KHZ_96KBITRATE_MONO_MP3 | audio-24khz-96kbitrate-mono-mp3 |
WEBM_24KHZ_16BIT_MONO_OPUS | webm-24khz-16bit-mono-opus |
该服务不返回其他格式的音频,例如WAV,PCM或其他MP3比特率.
流化综合
// Stream audio data in real-time
for await (const chunk of tts.synthesizeStream("Long text to stream...", 'en-US-AriaNeural')) {
// Process each audio chunk as it arrives
console.log(`Received chunk: ${chunk.length} bytes`);
}
提前离开循环 break 关闭连接.
取消和超时
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
try {
await tts.synthesize("Hello!", 'en-US-AriaNeural', { signal: controller.signal, timeout: 10000 });
} catch (error) {
if (error.name === 'AbortError') console.log('Cancelled');
}
signal 与 synthesize(), (中文(简体) ), synthesizeStream() 财务报告和审定财务报表 generate()。 。 。 . timeout 是等待多长时间而不收到数据,30秒默认.
音频信息
获取音频细节
await tts.synthesize("Hello, world!");
const audioInfo = tts.getAudioInfo();
console.log(`Size: ${audioInfo.size} bytes`);
console.log(`Format: ${audioInfo.format}`);
console.log(`Duration: ${audioInfo.estimatedDuration} seconds`);
仅获取持续时间
const duration = tts.getDuration();
console.log(`Audio duration: ${duration} seconds`);
导出选项
导出为 Base64
await tts.synthesize("Hello, world!");
const base64Audio = tts.toBase64();
console.log(`Base64 length: ${base64Audio.length}`);
导出为 Raw 缓冲
const rawAudio = tts.toRaw(); // Alias for toBase64()
const buffer = tts.toBuffer(); // Get as Buffer object
导出为文件
const filePath = await tts.toFile("output_audio");
console.log(`Audio saved to: ${filePath}`);
// Creates: output_audio.mp3
await tts.toFile("speech.mp3"); // Creates: speech.mp3 (the extension is not repeated)
词句边界
抵消和持续时间为100秒(每秒10 000 000克).
await tts.synthesize("Hello, world. How are you?", 'en-US-AriaNeural');
const words = tts.getWordBoundaries(); // [{ type: 'WordBoundary', offset, duration, text: 'Hello' }, ...]
const sentences = tts.getSentenceBoundaries(); // [{ type: 'SentenceBoundary', offset, duration, text: 'Hello, world.' }, ...]
// Save word boundaries to a JSON file
await tts.saveMetadata('metadata.json');
字幕
每句1个提示,加上正文的出处.
import { writeFile } from 'fs/promises';
import { toSRT, toVTT } from '@andresaya/edge-tts';
await tts.synthesize("¿Hola? Esto es una prueba.", 'es-CO-SalomeNeural');
await writeFile('speech.srt', tts.toSRT());
await writeFile('speech.vtt', tts.toVTT());
// From a generate() result
const result = await tts.generate("Hello, world.", 'en-US-AriaNeural');
await writeFile('hello.srt', toSRT(result.sentenceBoundaries));
实例
随语音选择完成示例
import { EdgeTTS } from '@andresaya/edge-tts';
async function textToSpeechExample() {
const tts = new EdgeTTS();
// Get available English voices
const englishVoices = await tts.getVoicesByLanguage('en-US');
console.log(`Available English voices: ${englishVoices.length}`);
// Use the first available voice
const voice = englishVoices[0];
console.log(`Using voice: ${voice.FriendlyName}`);
// Synthesize with custom options
await tts.synthesize(
"This is a test of the Edge TTS system with custom voice parameters.",
voice.ShortName,
{
pitch: '+10Hz',
rate: '-10%',
volume: '90%'
}
);
// Get audio information
const info = tts.getAudioInfo();
console.log(`Generated audio: ${info.size} bytes, ${info.estimatedDuration.toFixed(2)}s`);
// Save to file
const outputPath = await tts.toFile('./output/speech');
console.log(`Audio saved to: ${outputPath}`);
}
textToSpeechExample().catch(console.error);
流出示例
import { EdgeTTS } from '@andresaya/edge-tts';
import { createWriteStream } from 'fs';
async function streamingExample() {
const tts = new EdgeTTS();
const writeStream = createWriteStream('streaming_output.mp3');
const longText = "This is a very long text that will be streamed...";
for await (const chunk of tts.synthesizeStream(longText, 'en-US-AriaNeural')) {
writeStream.write(chunk);
console.log(`Streamed ${chunk.length} bytes`);
}
writeStream.end();
console.log('Streaming completed!');
}
streamingExample().catch(console.error);
语音探索示例
import { EdgeTTS } from '@andresaya/edge-tts';
async function exploreVoices() {
const tts = new EdgeTTS();
// Get all voices
const allVoices = await tts.getVoices();
console.log(`Total voices available: ${allVoices.length}`);
// Group by language
const languages = [...new Set(allVoices.map(v => v.Locale.split('-')[0]))];
console.log(`Languages available: ${languages.join(', ')}`);
// Get Spanish voices
const spanishVoices = await tts.getVoicesByLanguage('es');
console.log(`Spanish voices: ${spanishVoices.length}`);
// Get female voices
const femaleVoices = await tts.getVoicesByGender('Female');
console.log(`Female voices: ${femaleVoices.length}`);
// Test different voices
const testText = "Hola, este es un ejemplo de síntesis de voz.";
for (const voice of spanishVoices.slice(0, 3)) {
console.log(`Testing voice: ${voice.FriendlyName}`);
await tts.synthesize(testText, voice.ShortName);
const filePath = await tts.toFile(`./voices/${voice.ShortName}`);
console.log(`Saved: ${filePath}`);
}
}
exploreVoices().catch(console.error);
浏览器支持
这个库可以通过CDN或ES模块在网页浏览器中直接被使用.
重要内容: 直接连接, 合成只在 微软边缘。语音服务拒绝用户代理不是Edge的连接,一个网页不能更改其浏览器发送的用户代理。 Chrome,Firefox和Safari在合成时得到一款403. 对于这些浏览器,请使用中继器(见 其他浏览器) (中文(简体) ). 节点.js版本没有受到影响.
CDN 使用量 (UMD)
<!-- Load from CDN -->
<script src="https://unpkg.com/@andresaya/edge-tts@latest/dist/browser/edge-tts.umd.min.js"></script>
<script>
const tts = new EdgeTTS();
// Get available voices
tts.getVoices().then(voices => {
console.log('Available voices:', voices.length);
});
// Synthesize speech
async function speak() {
await tts.synthesize("Hello from the browser!", 'en-US-AriaNeural');
const audioData = tts.getAudioData();
// Play audio
const audioBlob = new Blob([audioData], { type: 'audio/mp3' });
const audioUrl = URL.createObjectURL(audioBlob);
const audio = new Audio(audioUrl);
audio.play();
}
</script>
ES 模块导入
<script type="module">
import { EdgeTTS } from 'https://unpkg.com/@andresaya/edge-tts@latest/dist/browser/edge-tts.esm.min.js';
const tts = new EdgeTTS();
// Use the library
const voices = await tts.getVoices();
console.log(voices);
</script>
浏览器中的流支持
<script type="module">
import { EdgeTTS } from 'https://unpkg.com/@andresaya/edge-tts@latest/dist/browser/edge-tts.esm.min.js';
const tts = new EdgeTTS();
const chunks = [];
// Stream audio chunks in real-time
for await (const chunk of tts.synthesizeStream("Long text to stream...", 'en-US-AriaNeural')) {
chunks.push(chunk);
console.log(`Received chunk: ${chunk.length} bytes`);
}
// Combine and play all chunks
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
const audioData = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
audioData.set(chunk, offset);
offset += chunk.length;
}
const blob = new Blob([audioData], { type: 'audio/mp3' });
const audio = new Audio(URL.createObjectURL(blob));
audio.play();
</script>
其他浏览器 (Relay)
在Node.js运行的小型中继器会用Edge头连接到服务并传递消息. 开始实例中继 :
node examples/relay-server.mjs # ws://127.0.0.1:8080
用浏览器指向它:
const tts = new EdgeTTS({ wssUrl: 'ws://127.0.0.1:8080' });
await tts.synthesize("Hello from Chrome!", 'en-US-AriaNeural');
这个例子只能听 127.0.0.1。如果您部署它,请在您自己的认证或原产地检查之后放置它,这样它就不会向所有人开放.
完整浏览器示例
关于语音选择和合成的完整实例,见 examples/browser-standalone.html。 。 。 .
语音选项
综合参数
| 参数 | 类型 | 范围 | 说明 |
|---|---|---|---|
pitch | string | number | -100Hz 改为 +100Hz | 语音音标调整 |
rate | string | number | -100% 改为 +200% | 语音率调整 |
volume | string | number | -100% 改为 +100% | 数量调整 |
参数示例
// Using numbers (recommended)
{ pitch: 20, rate: -10, volume: 90 }
// Using strings
{ pitch: '+20Hz', rate: '-10%', volume: '90%' }
// Mixed usage
{ pitch: 15, rate: '25%', volume: 85 }
处理错误
库提出的错误是: EdgeTTSError 实例 code从Node.js(例如: ECONNREFUSED)通过时不变.
code | 何时 |
|---|---|
HANDSHAKE | 服务拒绝连接. status 在Node.js(通常是403)中持有 HTTP 状态. |
NETWORK | 打开后连接失败( 浏览器) . |
TIMEOUT | 未收到数据 timeout m. |
NO_AUDIO | 尚未合成, 或服务未返回音频( 不同脚本中的语音和文本, 或仅标出平分的文本) . |
REFUSED | 该服务拒绝了请求,并说为什么,例如: Unsupported voice xx-XX-NobodyNeural. 或不支持的输出格式. status 持有WebSocket的近接码(1007). |
INVALID_OPTION | 文本为空,或 pitch, (中文(简体) ), rate 或者说 volume 无效. |
VOICES | 语音列表请求失败 . status 持有 HTTP 状态 . |
取消一个 AbortSignal 以信号的理由拒绝,一个 AbortError。 。 。 .
import { EdgeTTS, EdgeTTSError } from '@andresaya/edge-tts';
async function handleErrors() {
const tts = new EdgeTTS();
try {
await tts.synthesize("Test", 'en-US-AriaNeural', { volume: -150 });
} catch (error) {
if (error instanceof EdgeTTSError && error.code === 'INVALID_OPTION') {
console.error('Invalid parameter:', error.message);
}
}
try {
// This will throw an error - no audio data
const duration = tts.getDuration();
} catch (error) {
console.error(error.code, error.message); // NO_AUDIO ...
}
}
许可证
该项目根据GNU通用公共许可证v3(GPLv3)获得许可.
承认
我们谨感谢下列项目的开发者和捐助者的启发和基础工作: