answer-me-with-html:用 ElevenLabs 为解释视频配音——环境变量、语音模型选择、缓存机制与报错排查
【免费下载链接】answer-me-with-html
Answer me with HTML — an agent skill that answers hard questions with a one-page HTML you can actually read. 让 AI Agent 用一页 HTML 回答复杂问题。
am video 可以把 Markdown 稿件渲染成带旁白的解释视频,而 ElevenLabs 是它可选的云端配音后端:只要设置了 ELEVENLABS_API_KEY,旁白就会自动交给 ElevenLabs 合成;没有 key 时则回退到系统语音,因此整套配置是可选项。本文覆盖 ElevenLabs 接入的完整流程——创建最小权限 API key、三个环境变量(ELEVENLABS_API_KEY / ELEVENLABS_VOICE_ID / ELEVENLABS_MODEL_ID)的取值与默认值、按声音/模型/文本三级维度的本地缓存如何让你只为改动的句子付费,以及 401/402/超时三类报错的源码级来源与排查方法。
配音在 am video 中的位置:一条有明确回退顺序的语音管线
在动手配置之前,先理解 ElevenLabs 在整体配音链路里的位置。src/video/tts.js 文件头的注释写明了回退顺序:
ElevenLabs(设置了
ELEVENLABS_API_KEY时)→ 系统 TTS(macOSsay/ Linuxespeak-ng)→ 仅字幕。
--voice 参数共有五个取值:auto | elevenlabs | local | system | off。pickProvider 函数实现了具体决策逻辑,其中与 ElevenLabs 直接相关的分支有两条:
- 显式指定
--voice elevenlabs时,如果ELEVENLABS_API_KEY缺失会直接抛出voice=elevenlabs needs the ELEVENLABS_API_KEY environment variable,即文档里"没有 key 时直接报错"的行为; - 默认
--voice auto时(也是 src/config.js 中voice配置项的默认值),只要检测到ELEVENLABS_API_KEY就选用 ElevenLabs,否则继续回退到系统语音。
这也解释了文档里"有 key 就自动用 ElevenLabs"这一句话背后的真实判断链。
配置步骤:最小权限 key + 一个环境变量
官方配置指南见 docs/elevenlabs.zh-CN.md(英文版 docs/elevenlabs.md),完整步骤如下。
-
在 ElevenLabs 后台创建 API key。只勾选 Text to Speech(语音合成)权限就够了,
am不调用别的接口——从源码看,elevenLabs 提供者 只向一个端点发请求:POST https://api.elevenlabs.io/v1/text-to-speech/{voice_id}?output_format=pcm_22050,认证方式是请求头xi-api-key,请求体只有text和model_id两个字段,确实不需要其他任何 API 权限。 -
设置环境变量,再渲染:
export ELEVENLABS_API_KEY="sk_..." # 想长期生效,就把这行写进 ~/.zshrc am video draft.md # 有 key 就自动用 ElevenLabs am video draft.md --voice elevenlabs # 没有 key 时直接报错
两点实操注意事项,文档里明确强调了:
- Agent 只能读到启动它之前已经设好的变量。如果你是在 Agent 会话中途
export的 key,Agent 的子进程未必能继承到; - 想把 ElevenLabs 固定为默认配音,运行
am config set voice elevenlabs。该命令写入的是~/.answer-me-with-html/config.json(可用AM_HOME改位置),voice键在 CONFIG_KEYS 中被声明为枚举类型,合法值即auto | elevenlabs | local | system | off,test/config.test.js 验证了默认值为auto。这样无论环境变量如何变化,渲染都会强制走 ElevenLabs 分支,缺 key 时报错而不是静默回退。
选声音和模型(可选):两个变量与它们的默认值
声音和模型适合的语言、风格因人而异,请按自己的需求选:
| 变量 | 作用 | 不设时 |
|---|---|---|
ELEVENLABS_VOICE_ID | 声音。在 ElevenLabs 的声音页复制 ID | bIHbv24MWmeRgasZH58o(Will,官方声音) |
ELEVENLABS_MODEL_ID | 模型,如 eleven_v4_turbo、eleven_flash_v2_5、eleven_multilingual_v2 | eleven_v4_turbo |
这两个默认值在源码中是硬编码常量 ELEVEN_DEFAULT_VOICE / ELEVEN_DEFAULT_MODEL,读取逻辑就是简单的 env.ELEVENLABS_VOICE_ID || ELEVEN_DEFAULT_VOICE。am help video 的输出中也同样说明了这两个变量及 eleven_v4_turbo 的默认模型。
关于默认声音的语言能力,文档给出了一个容易踩坑的细节:
- 默认值(Will +
eleven_v4_turbo)在免费套餐下可用,中英文稿件都能念; - Will 母语是英语,ElevenLabs 只在
eleven_multilingual_v2下把中文列为它的已验证语言。想要听起来是母语的中文,请自己选中文声音; - 公共声音库里的声音需要付费套餐,免费套餐调用时 API 会返回
402 paid_plan_required。
各模型支持哪些语言,以 ElevenLabs 官方的模型列表为准(其文档在 ElevenLabs 官网可查,此处不附外部链接)。
费用与缓存:每句旁白只合成一次
ElevenLabs 按字符计费。为了让"改一句只花一句的钱"成立,am 把每句旁白单独合成并缓存:
- 缓存目录是
~/.answer-me-with-html/cache/tts/(即AM_HOME/cache/tts/),每个缓存文件是以哈希值命名的.pcm文件; - 从 synthAll 的实现看,缓存键是
sha1(provider.id + 文本),而 ElevenLabs 的provider.id形如elevenlabs:{voice_id}:{model_id}(见 tts.js 第 54 行)——也就是说缓存确实按"声音 + 模型 + 文本"三个维度区分。只有改过的句子,或换了声音、模型,才会重新请求计费; - 音频统一为 22050 Hz 单声道 16 位 PCM,ElevenLabs 的请求直接在 URL 上要求
output_format=pcm_22050,省去转码; - 缓存写入采用"先写临时文件再 rename"的方式(writeCache),中断不会留下半个缓存文件;损坏(空文件、奇数字节)的缓存会被当作未命中重新合成(readCache);
- 多个句子并发合成,ElevenLabs 提供者的并发上限为 2(tts.js 第 55 行)。
顺带一提,缓存是"可再生的",所以 src/housekeeping.js 中的 am clean 会直接清空整个 cache/ 目录(连同超过 30 天的页面和视频),配置文件不受影响;test/housekeeping.test.js 对这一行为有测试覆盖。
报错排查:三种消息对应的三种原因
命令行提示是英文的,下面把文档的对照表与源码中的抛出点放在一起:
| 报错 | 原因 | 源码出处 |
|---|---|---|
voice=elevenlabs needs the ELEVENLABS_API_KEY environment variable | 显式指定了 --voice elevenlabs 但 key 没设 | pickProvider |
ElevenLabs returned 401 或 missing_permissions | key 不对,或没有语音合成权限 | HTTP 状态透传 |
ElevenLabs returned 402,含 paid_plan_required | 用了声音库里的声音,而你是免费套餐;换官方声音或升级套餐 | 同上 |
Cannot connect to ElevenLabs | 60 秒内没有响应,或没有网络;加 --voice off 只出字幕 | fetch 超时分支 |
两点补充:
- 60 秒超时来自常量
ELEVEN_TIMEOUT_MS = 60000(tts.js 第 15 行),通过AbortSignal.timeout实现;超时与网络不可达都会被包装成Cannot connect to ElevenLabs,并在超时场景下追加no response within 60 seconds的原因说明。 --voice off会让 pickProvider 返回null,即纯字幕模式。测试 test/cli.test.js 第 428 行 还验证了一个相关行为:用--voice off生成的视频页面在后续am patch时保持无声,不会因为全局 config 改了配音而"被重新配音"。
延伸阅读
- 视频渲染的完整行为(逐步构建、镜头聚焦、本地
--voice local配音等):docs/video.zh-CN.md voice等全部配置项的默认值与取值:docs/reference.zh-CN.md- 技能自带的视频参考(Agent 侧的配音决策规则):skills/answer-me-with-html/references/video.md
- 配音管线的完整实现:src/video/tts.js
【免费下载链接】answer-me-with-html
Answer me with HTML — an agent skill that answers hard questions with a one-page HTML you can actually read. 让 AI Agent 用一页 HTML 回答复杂问题。
更多推荐

所有评论(0)