语音共读 Skill
把一本书变成一位会提问的领读人。所有重活(解析、切章、抽取、合成、记忆)都在本地脚本完成,LLM 只在出题和答疑时被调用一次,输入恒定在 1500 字以内。
快速开始
cd <skill目录>
pip install -r requirements.txt # 首次
python voiceread.py doctor # 自检
python voiceread.py engines --install-piper # 装离线备胎(强烈建议,给 edge-tts 上保险)
python voiceread.py info 书.epub # 看目录
python voiceread.py read 书.epub --chapter 3 # 读第3章并互动
python voiceread.py read 书.pdf --resume # 接着上次读
python voiceread.py memory show # 看阅读画像
核心命令
| 命令 | 用途 |
|---|---|
doctor |
环境自检,装完先跑 |
info <书> |
章节目录、字数、时长、已读状态 |
read <书> |
共读主流程:朗读 → 问答 → 思考 → 记忆 |
quiz <书> |
只出题不朗读,可批量预生成 |
export <书> |
只导出有声书 mp3,不互动 |
memory show/book/search/export |
阅读记忆管理 |
list |
列出所有读过的书 |
voices |
列出可用中文音色 |
engines |
查看 TTS 引擎状态 / 安装离线备用引擎 |
config --set k=v |
持久化配置 |
co readers/compare/leaderboard/reflections |
多人共读:共享目录上的进度对比 / 排行榜 / 互看思考(P1 新增) |
章节控制
--chapter 5 # 第5章
--chapter 3-7 # 第3到7章
--chapter 1,5,9 # 指定几章
--chapter 8- # 第8章到末尾
--chapter all # 全书
--resume # 从上次未读处继续
语音控制
--speed 1.5 # 倍速 0.5-3.0
--voice-preset storyteller # default/warm/pro/storyteller
--voice zh-CN-XiaoxiaoNeural # 直接指定正文音色
--no-prosody # 关掉感情节奏,平读
感情是怎么做出来的:edge-tts 已不支持 SSML 情感标签,所以用三层模拟——
① 角色分工:标题/正文/对话/强调/旁注/提问/答案各用不同音色;
② 韵律偏移:每种角色有独立的语速与音高微调;
③ 呼吸停顿:按标点和段落边界插入 120–900ms 不等的静音。
第③点对"像人在读"的贡献最大。
声音克隆(可选):部署 CosyVoice2 或 GPT-SoVITS 后
--tts-engine cosyvoice --clone-ref-audio 我的声音.wav --clone-ref-text "参考音频里说的话"
详见 docs/CLONE_SETUP.md。
离线备用引擎(保命机制,P0 新增)
edge-tts 走的是微软 Edge 浏览器的非官方接口,没有任何 SLA——接口一旦变更或封禁,
整条免费朗读链路直接断掉。为此新增了 piper 离线 TTS 作为备胎:模型下好就永远能用,
质量不如 edge 但足够听清楚(这是「能不能用」和「好不好听」的区别)。
voiceread engines # 看各引擎状态 + 当前降级策略
voiceread engines --install-piper # 下载离线模型(约 60MB,一次即可永久离线)
降级规则(默认 tts-fallback=auto,fallback-after-failures=2):
- 主引擎(默认 edge)某一段失败 → 这一段自动用备用引擎救回,朗读不中断;
- 主引擎连续失败 2 段 → 永久切到备用引擎,其余段全部走备胎;
- 单段因「空文本」等参数错误失败 → 不算引擎故障,不会误触发降级;
- 两段都失败 → 抛可读错误并跳过该段,绝不静默吞掉。
缓存隔离:降级时备胎的输出会写到它自己的缓存槽位,不会污染主引擎缓存
(避免「串味」——同一段文字下次主引擎恢复后读到的是正确的音色,而非残留的备胎音)。
> 音色的现实:piper 官方中文模型目前只有 huayan 一个女声(medium / x_low)。
> edge 那套 8 角色音色分工在 piper 下无法原样复现,这里把每个 edge 音色映射成一组
> 「音高 + 语速」偏移,在单模型上造出可分辨的角色差异(详见 docs/PIPER_SETUP.md)。
播放控制(P0 新增)
听书和听歌不一样:听不懂要倒回去,走神了要重听这一句,赶时间要加速,精读时希望每句停一下。
新增交互式播放器(依赖 ffplay,由 doctor 自检;拿不到真终端按键时自动降级为整段播放):
voiceread read 书.epub --chapter 3 # 播放时自动开启按键控制
voiceread read 书.epub --no-interactive # 关掉,整段播完为止
播放中可用按键:
| 按键 | 作用 |
|---|---|
空格 / p |
暂停 / 继续(真暂停:杀进程记位置,恢复时从原处续播) |
← / → |
后退 / 前进 10 秒 |
↑ / ↓ (或 , / .) |
上一句 / 下一句 |
r |
重听当前句(计入「没听懂」次数,是薄弱点的强信号) |
l |
逐句模式开关(每句念完自动停,精读用) |
[ / ] |
减速 / 加速 0.1×(0.5×–3.0× 夹紧) |
0–9 |
跳到 10%–90% 处 |
b |
打书签(记位置 + 当前句文本) |
t |
显示当前句全文 |
n |
结束播放,进入问答环节 |
q |
退出共读(进度自动保存,下次 --resume 续听) |
h |
显示这份帮助 |
断点续听:每章播到哪记到哪(resume-position=1),下次 --resume 从原位置接着播;
退出时还会把续听点存进阅读状态,重读同一章也不会从头来。
互动模式
--asr # 用嘴说话提问(需 faster-whisper + sounddevice)
--silent # 纯文字,不合成语音
--no-quiz # 只朗读不互动
--no-play # 只生成音频不播放
不加 --asr 时默认打字输入,两种模式随时可切。
出题机制(token 成本关键)
每章流程固定:本地 TextRank 压缩 → 一次 LLM 调用 → 落盘缓存。
无论章节 3000 字还是 6 万字,送进模型的 prompt 恒定约 1200 token。
三种模式自动降级:
| 模式 | 触发条件 | 说明 |
|---|---|---|
api |
配了 llm_api_key |
脚本直连廉价 API,不占用 agent 上下文,推荐 |
agent |
--quiz-mode agent |
导出 prompt 包,由调用 skill 的 agent 回填 |
local |
没配 key | 纯本地规则出题,零成本,质量一般 |
配置 API:
python voiceread.py config --set llm_api_key=sk-xxx llm_model=deepseek-chat
出题结果缓存在 ~/.voicereader/quiz/,重读同一章不重复付费。
自适应出题深度(P1 新增)
出题不再固定 5 题,而是按章节类型与长度自动调题数和侧重点:
- 类型检测(离线启发式):扫描章节前 8k 字,按线索词判定
理论 / 案例 / 操作 / 叙事 / 综合,信号太弱回退综合。 - 理论章 → 侧重「是什么 / 为什么 / 常见误解」
- 案例章 → 侧重「关键决策 / 如果换做法 / 证据」
- 操作章 → 侧重「步骤顺序 / 具体做法 / 踩坑」
- 叙事章 → 侧重「人物动机 / 情节因果」,且题数 −1 不打断情节流
- 长度自适应题数:短章 3 题、中章 5 题、长章 7 题(叙事章各档 −1)。
- 给 LLM 的 prompt 会注入「本章类型 + 路由指引 + 题数」;本地降级模式按
类型选不同问法模板。
想强制固定题数(如统一 7 题)用 --quiz-count 7;想看某章自动识别结果:
voiceread quiz <书> -c 3。
先问后答的交互流
朗读整章
→ 第1题:读出问题 → 等你作答 → 揭晓答案 → 自评对错
→ …5题…
→ 开放性思考题(无标准答案)+ 思考支架
→ 自由提问(基于本章摘要作答,不重喂原文)
→ 延伸荐书 2 本
→ 写入记忆
任何环节回车跳过,s 跳过整个问答,q 退出(进度自动保存)。
阅读记忆(给 agent 用)
三层结构,位于 ~/.voicereader/memory/:
| 层 | 文件 | 内容 | 谁读 |
|---|---|---|---|
| L1 | events.jsonl |
原始事件流,append-only | 脚本 |
| L2 | books/<id>.md |
单书档案:进度、要点、心得、提问、薄弱点 | 需要细节时 |
| L3 | profile.md |
跨书画像:书目、主题、思考片段 | agent 默认读这个 |
agent 该怎么用:
python voiceread.py memory show # 读 L3 画像(约 1-2k token)
python voiceread.py memory book --book-id <id> # 读某本书的详细档案
python voiceread.py memory search -q "复利" # 检索历史记录
python voiceread.py memory export -o mem.md # 导出完整记忆包
L3 画像里自带给 AI 的使用说明。回答用户问题时若涉及读过的书,应先查 L3,
需要细节再读对应 L2,不要读 L1——那是给脚本用的原始日志。
这样设计的好处:记忆可以无限增长,但注入 prompt 的成本恒定。
多人共读模式(P1 新增)
把同一个目录用网盘同步(OneDrive / 百度网盘 / Dropbox 任一),
两位或多位伙伴各自用 --reader <名字> --shared <那个目录> 朗读同一本书,
事件就会写进 <共享目录>/readers/<名字>/events.jsonl,互不干扰又共享可见。
私人记忆(~/.voicereader/memory/)和共享记忆双写:你自己的进度永远留本地,
共读数据额外同步到共享目录。任何一方随时能查彼此的进度。
# 小明和小红各自朗读,事件落到共享目录的 readers/小明、readers/小红
voiceread read 书.epub -c 3 --shared ~/共读 --reader 小明
voiceread read 书.epub -c 3 --shared ~/共读 --reader 小红
# 之后任意一方查看共读情况
voiceread co readers --shared ~/共读 列出共读伙伴
voiceread co compare --book <id> --shared ~/共读 进度 / 正确率对比
voiceread co leaderboard --shared ~/共读 答题正确率排行榜(带奖牌)
voiceread co reflections --book <id> --shared ~/共读 互看开放思考题回答
voiceread co reflections --book <id> --reader 小红 --shared ~/共读 只看某人的回答
--shared 也可在 config --set shared_memory=<目录> 里持久化,之后省略。
共享目录里若还没人读,命令会提示「共读模式需要先指定共享目录」而不是崩溃。
> 共享目录建议用网盘同步文件夹,这样多位伙伴各自本地朗读、云端汇合,
> 无需自建服务器。详见 docs/CO_READ.md。
支持格式
| 格式 | 切章策略(三级 fallback) |
|---|---|
| 书签目录 → 字号聚类 → 标题正则 → 等长切分 | |
| EPUB | nav/ncx 目录 → spine 顺序 → h1/h2 |
| MD/TXT | 标题层级自动选择 → 标题正则 → 等长切分 |
解析结果缓存在 ~/.voicereader/library/,--reparse 强制重解析。
常见问题
没声音 → doctor 看 ffmpeg 是否安装;Linux 需要 mpv/mpg123 之一。
PDF 提取为空 → 大概率是扫描版,需要先 OCR。
出题质量差 → 说明在用 local 模式,配个 API key 就好。
想换音色 → voices 命令列出全部 300+ 音色。
edge-tts 突然报错 / 整段朗读中断 → 它没有 SLA,接口可能临时封禁。装离线备胎:
voiceread engines --install-piper(还需 pip install piper-tts),之后会自动降级到本地引擎。
播放中没有按键反应 → 交互模式依赖 ffplay 且需要真终端(tty);doctor 会告诉你是否可用,
或加 --no-interactive 退化为整段播放。详见 docs/PIPER_SETUP.md。
只有 imageio-ffmpeg(没装系统 ffmpeg) → skill 会自动探测它提供的 ffmpeg,合成/转码/拼接照常工作;
但 imageio-ffmpeg 不含 ffplay,所以播放控制(暂停/跳句)用不了,需 winget install ffmpeg 装完整版才解锁。