Edge-TTS 实战:用 Python 免费把文本合成自然语音

做语音助手、给文章配有声版、批量生成短视频配音时,第一步往往都是“把文字变成声音”。过去要拿到自然的语音,要么用系统自带、机械感很强的离线引擎,要么接入付费的云 TTS 服务。Edge-TTS 的出现改变了这一点:它是微软开源的 文本转语音(TTS) 库,直接调用与 Edge 浏览器同源的在线神经语音,音质接近真人,关键是完全免费、无需申请 API Key。本文从原理到实战,带你用 Python 把任意文本合成为自然流畅的语音文件,并封装成可调用的服务。

一、TTS 是什么:从拼接合成到神经网络语音

文本转语音(Text-To-Speech,简称 TTS)的目标,是让机器把一段文字“读”出来。早期方案是“拼接合成”:把录好的音素、音节裁剪拼接,声音生硬、断句怪异;后来演进到“参数合成”,用声码器建模声学特征,流畅度提升但仍显机械。如今主流是神经网络语音(Neural TTS):模型直接端到端学习“文字→波形”,能自然处理语气、停顿和重音,听感已非常接近真人。微软的在线神经语音就属于这一代,而 Edge-TTS 让我们无需付费就能免费使用它。

二、为什么选 Edge-TTS

横向对比几个常见方案,Edge-TTS 的优势很明显:它基于微软云端神经语音,音质高、音色多、支持中文在内的数十种语言,而且对个人开发者零门槛——不用注册、不用 Key、没有免费额度焦虑。相比完全离线但机械的 pyttsx3,它胜在“像人”;相比同样高质量的 Azure TTS,它胜在“免费”。代价是合成时必须联网调用微软服务(并非离线引擎),且不适合对延迟和并发有极端要求的生产核心链路。对大多数个人项目、内部工具、内容创作来说,这个取舍非常划算。

2.1 几类方案的定位

方案音质是否免费是否需联网适用场景
Edge-TTS神经语音·高是(免 Key)是个人项目、内容配音、原型
pyttsx3离线·机械是否无网络环境、简单提示音
Azure TTS神经语音·高付费是企业级、需 SLA 保障
CosyVoice / ChatTTS神经语音·高开源自部署自部署需要私有化、定制音色

三、安装与环境准备

Edge-TTS 是一个纯 Python 包,底层走 WebSocket 与微软服务通信。安装只需一行,Python 3.8+ 均可。

# 安装
pip install edge-tts

# 命令行直接试一句(无需写代码)
edge-tts --text "你好,这是一段测试语音" --voice zh-CN-XiaoxiaoNeural --write-media hello.mp3

如果命令行能正常生成 hello.mp3,说明网络与运行环境都没问题,接下来就可以用 Python API 精细控制了。需要编写脚本时,用 VS Code 这类编辑器配合终端运行最顺手。

四、第一行:把文本合成成语音文件

Edge-TTS 是异步库(基于 asyncio),核心对象是 Communicate。传入文本和音色名,调用 save() 即可落盘为 mp3。

import asyncio, edge_tts

TEXT = "欢迎使用 Edge-TTS,这是一段由神经网络生成的语音。"
VOICE = "zh-CN-XiaoxiaoNeural"   # 微软晓晓,中文女声

async def main():
    communicate = edge_tts.Communicate(text=TEXT, voice=VOICE)
    await communicate.save("output.mp3")
    print("已生成 output.mp3")

asyncio.run(main())

就这么几行,你就拥有了一段接近真人的中文朗读。注意所有调用都写在 async 函数里,最后用 asyncio.run() 启动事件循环——这是 Edge-TTS 的硬性约定,忘记 await 或没跑事件循环是新手最常见的报错来源。

五、选音色与语言:先列一遍可用声音

Edge-TTS 内置上百种音色,覆盖中、英、日、韩等多语言,每种语言还有男女声与不同风格(如“晓晓”“云希”“云扬”)。先列出全部可用音色,再挑合适的:

import asyncio, edge_tts

async def list_voices():
    voices = await edge_tts.list_voices()
    for v in voices:
        # ShortName 形如 zh-CN-XiaoxiaoNeural
        if v["Locale"].startswith("zh") or v["Locale"].startswith("en"):
            print(v["ShortName"], v["Locale"], v["Gender"])

asyncio.run(list_voices())

常用的中文音色有 zh-CN-XiaoxiaoNeural(女声·温柔)、zh-CN-YunxiNeural(男声·活泼)、zh-CN-YunyangNeural(男声·新闻播报风);英文可选 en-US-AriaNeural、en-US-GuyNeural 等。音色名统一以“语言-地区-名字”命名,切换多语种朗读时改一个 VOICE 变量即可。

六、用 SSML 调节语速、音调和停顿

单纯传文本时,语速、停顿都由模型默认处理。若要更精细控制——比如给儿童内容放慢语速、在标题后加长停顿——可以用 SSML(语音合成标记语言)。Edge-TTS 接受 SSML 字符串作为输入,通过 <prosody> 调速率/音高,用 <break> 插入停顿。

import asyncio, edge_tts

SSML = '''<?xml version="1.0" encoding="utf-8"?>
<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="zh-CN">
  <voice name="zh-CN-XiaoxiaoNeural">
    大家好,欢迎收听本期技术播客。
    <break time="600ms"/>
    <prosody rate="0.9" pitch="+3%">今天我们用更慢的语速,讲清楚 Edge-TTS 的玩法。</prosody>
  </voice>
</speak>'''

async def main():
    # 传入 SSML 时,voice 参数可省略(由 <voice> 标签决定)
    communicate = edge_tts.Communicate(text=SSML, voice=None)
    await communicate.save("ssml_output.mp3")

asyncio.run(main())

rate 取值如 0.9(慢 10%)、1.2(快 20%);pitch 用百分比或半音;break time 支持 ms 或 s。合理加停顿和语速变化,能显著提升听感,尤其适合做有声书、课程录音。

七、长文本分片与批量合成

微软单次合成有长度和时长的上限(通常数千字符、音频约 10 分钟封顶)。给长文配音时,必须先把文本按句号、段落切分成片段,逐段合成,再拼接。下面给出一个“按标点断句 + 顺序合成”的稳健写法:

import asyncio, re, edge_tts

def split_text(text, max_len=200):
    # 优先按标点断句,避免把一句话拦腰截断
    parts, buf = [], ""
    for seg in re.split(r"(?<=[。!?.!?])", text):
        if len(buf) + len(seg) <= max_len:
            buf += seg
        else:
            if buf: parts.append(buf)
            buf = seg
    if buf: parts.append(buf)
    return parts

async def synthesize(long_text, voice, out_prefix):
    for i, chunk in enumerate(split_text(long_text)):
        c = edge_tts.Communicate(text=chunk, voice=voice)
        await c.save(f"{out_prefix}_{i:03d}.mp3")
        print(f"片段 {i} 完成,{len(chunk)} 字")

# 之后用 ffmpeg 把 001/002... 按顺序拼接成完整音频即可
# ffmpeg -f concat -safe 0 -i list.txt -c copy final.mp3

切分时按中文句号、问号、感叹号断句,既保证单段不超限,又不会在句子中间切断导致朗读生硬。多段合成完,用一行 ffmpeg 命令即可串成完整音频文件。

八、封装一个命令行 TTS 工具

把上面的能力收拢成一个可复用的小工具,支持“指定文本/文件、选音色、输出路径”,日常用起来更顺。

import asyncio, argparse, edge_tts

async def run(text, voice, out):
    await edge_tts.Communicate(text=text, voice=voice).save(out)

if __name__ == "__main__":
    p = argparse.ArgumentParser()
    p.add_argument("-t", "--text", help="要合成的文本")
    p.add_argument("-f", "--file", help="读取文本文件")
    p.add_argument("-v", "--voice", default="zh-CN-XiaoxiaoNeural")
    p.add_argument("-o", "--out", default="out.mp3")
    a = p.parse_args()
    src = open(a.file, encoding="utf-8").read() if a.file else a.text
    asyncio.run(run(src, a.voice, a.out))
    print("done ->", a.out)

# 用法:
# python tts.py -t "今天天气不错" -v zh-CN-YunxiNeural -o a.mp3
# python tts.py -f article.txt -o book.mp3

九、把它做成 API:配合 FastAPI 在线合成

若要集成进网页或 App,把 TTS 包成一个 HTTP 接口最方便。结合本站讲过的 FastAPI 流式 API 思路,下面用 StreamingResponse 把合成结果以音频流返回,前端拿到即可直接播放:

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import edge_tts, io, asyncio

app = FastAPI()

@app.get("/tts")
async def tts(text: str, voice: str = "zh-CN-XiaoxiaoNeural"):
    communicate = edge_tts.Communicate(text=text, voice=voice)
    buf = io.BytesIO()
    async for chunk in communicate.stream():
        if chunk["type"] == "audio":
            buf.write(chunk["data"])
    buf.seek(0)
    return StreamingResponse(buf, media_type="audio/mpeg",
                             headers={"Content-Disposition": "attachment; filename=tts.mp3"})

# 启动:uvicorn main:app --port 8000
# 调用:http://127.0.0.1:8000/tts?text=你好世界

这里的 stream() 会边合成边返回音频分片,配合流式响应能做到“边生成边播放”,体验比先生成完整文件再返回更好。把它用 Docker 打包后,就能稳定部署在内网或云服务器上,供多个业务共用。

环节要点
事件循环全程 await,入口用 asyncio.run 或 FastAPI 的 async 视图
音色选择中文首推 Xiaoxiao / Yunxi,多语切换改 voice 即可
长文本先按标点切分,再逐段合成、ffmpeg 拼接
返回方式小文件 save 落盘;在线服务用 StreamingResponse 流式返回

十、与语音识别组成闭环:STT + TTS

文本转语音只是语音交互的一半。把本文的 TTS 与语音识别(STT)结合,就能搭出一个最小可用的“语音对话”闭环:麦克风采集声音 → STT 转成文字 → 大模型生成回复 → TTS 读出来。这种组合特别适合做本地知识库的语音问答、无障碍朗读、自动播客生成等场景;若要更进一步,还能接上 Dify + Ollama 这类编排平台,把“听懂—思考—回答—朗读”串成一条流水线。

十一、常见坑与排障

现象原因与对策
RuntimeError: asyncio 事件循环相关确认逻辑写在 async 函数内,并用 asyncio.run 启动
返回空文件 / 无音频检查文本是否为空、网络是否可达微软服务(需联网)
长文本被截断单段超字符/时长上限,按第七节切分后再合成
中文乱码读取文本文件时指定 encoding=’utf-8′
并发过多被限流控制并发数,逐段顺序合成,必要时加重试与退避
音色名无效先用 list_voices() 列出真实 ShortName,避免手敲拼写错误

另外要记住:Edge-TTS 调用的是微软在线服务,必须能访问外网;在隔离内网里它会直接失败。对内网强隔离且要求离线的场景,应改用 pyttsx3 或自部署 CosyVoice 等方案。

十二、小结

Edge-TTS 用极低的门槛,把高质量神经 文本转语音 能力交到了每个 Python 开发者手里:免 Key、免费、音质自然、支持 SSML 精细调控。从一行 save() 到封装命令行工具,再到用 FastAPI 暴露成在线接口,它都能平滑胜任。把它和语音识别、大模型编排组合起来,你就能快速搭出语音助手、有声内容生产线这类过去“想做但嫌贵”的应用。下一步,不妨挑一篇你自己的文章,用文中的脚本生成一段朗读音频,亲耳感受一下神经网络语音的听感差异。

上一篇 Grafana 监控面板搭建实战:可视化与告警配置
下一篇 btop 实战:用 Rust 打造终端系统监控面板
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]