PPT配音skill
目的
把"做课件 → 写讲稿 → 配语音 → 发链接"这条链路标准化。核心是一步一音:
PPT 的每个内容块 = 一条逐字稿 = 一个音频文件,序号全局连续、严格一一对应,
从而实现演示时"翻到哪讲到哪",甚至全自动连续讲解。
何时使用
- 要做一份带语音讲解的演示/课件/培训材料
- 已有 PPT 或大纲,需要补讲解稿并配音
- 需要把配音课件发布成网页链接分享给别人
- 需要用指定人的声音(克隆)来讲课
- 想让课件带一个「AI 解答老师」:学员能配置大模型、实时提问、模型流式回答
硬性约定(贯穿全流程,不可违背)
- 一个 block = 一步 = 一条逐字稿 = 一个音频。全局序号从 0 起,跨页连续。
- 音频命名
{prefix}{序号补零}.{ext},序号从 1 起(= 全局序号 + 1)。 - 逐字稿行数 必须等于 block 总数,脚本会强校验。
- 交付前必须跑
verify_audio.py,缺失/空文件一律补齐后再交付。 - 改动 HTML 后必须做 JS 语法校验(见下)。
流水线
阶段 0 · 需求确认(缺什么问什么,别猜)
必须拿到:主题/受众/页数(或大纲)、是否要克隆音色。
- 要克隆 → 必须有参考音频与 ModelScope Token(ms-xxx),没有就问,不要编造默认值。
- 视觉风格:有参考海报/VI 就沿用其配色(写入 meta.colors);有 logo 就填 meta.logo。
阶段 1 · 内容策划:写 spec.json
按 assets/spec_example.json 的结构产出规格文件。先把大纲给用户确认再落盘成品。
block 的 5 种类型:
| type | 用途 | 字段 |
|---|---|---|
card |
通用卡片 | title, desc |
list |
要点列表 | title, items[] |
flow |
横向流程(N 步) | title, steps[{t,d}] |
quote |
金句/结论居中 | title, text |
kv |
键值表/对照表 | title, rows[[k,v]] |
设计原则:一页 2~6 个 block 为宜;每页一个明确论点;流程用 flow,结论用 quote。
阶段 2 · 渲染 PPT
python scripts/render_ppt.py --spec spec.json --out 演示.html --manifest steps.json
输出每页步数清单。--manifest 生成"步号→页码/标题"对照表,写逐字稿时按它对齐。
阶段 3 · 写讲解逐字稿
按 references/lecture-script-style.md 的规范写,产出 script.json
(与 block 顺序一致的对象数组),然后:
python scripts/write_script_xlsx.py --spec spec.json --script script.json --out 讲课逐字稿.xlsx
行数不等会直接报错退出——这是保护机制,不要绕过。
阶段 4 · 生成音频
路线 A · edge-tts(免费,快速出全量)
python scripts/gen_edge_tts.py --xlsx 讲课逐字稿.xlsx --out-dir audio \
--voice zh-CN-YunyangNeural --rate -3%
路线 B · 声音克隆(复刻指定人声)
python scripts/gen_clone_batch.py --xlsx 讲课逐字稿.xlsx --ref 参考.mp3 \
--token ms-xxxx --out-dir audio --prefix clone_step_ --pad 2
83 条约 35 分钟,用后台任务跑。细节与坑见 references/voice-cloning.md。
推荐组合:先跑 A 打通全流程,再跑 B 替换(文件名对齐即可,改 spec.json 的
audio.prefix 与 audio.ext 后重新渲染)。
阶段 5 · 校验与本地交付
python scripts/verify_audio.py --spec spec.json --audio-dir audio \
--pattern "clone_step_{n:02d}.mp3"
JS 语法校验(改完 HTML 必做):
python -c "import re;h=open('演示.html',encoding='utf-8').read();\
ms=re.findall(r'<script[^>]*>(.*?)</script>',h,re.S);open('_c.js','w',encoding='utf-8').write(max(ms,key=len))"
node --check _c.js && echo JS_OK && rm -f _c.js
然后 present_files 呈现 HTML。
阶段 5½ · 加 AI 解答老师(可选,默认 DeepSeek)
给课件加一个悬浮「🤖 AI 解答老师」面板:学员点开即可配置大模型(API Key / 模型 /
Base URL,存 localStorage,并自动读取同目录 Deepseek秘钥.txt),实时流式提问,回答逐字渲染;
勾选「附带当前课件页内容」会把正看的这一页文本作为上下文发给模型,答疑更贴题。
复用三个文件(id 严格对应,勿改):
- assets/ai_teacher.css → 内联进课件 <style>(紧挨 @media print 之前)
- assets/ai_teacher_panel.html → 放进 <body> 的 slides 之外
- assets/ai_teacher.js → 放在 </body> 前的 <script>
一键注入(推荐,最省事):渲染时直接加 --ai-teacher,脚本自动把上面三件套注入模板,
并自动给全局 keydown 翻页监听补上 INPUT/TEXTAREA 守卫(否则在 AI 输入框打字会被翻页拦截)。
课程专属人设用 --ai-system 人设.txt 传入,渲染时换成该文件内容:
python scripts/render_ppt.py --spec spec.json --out 演示.html \
--ai-teacher --ai-system 人设.txt
不传 --ai-system 时,面板用 ai_teacher.js 里的默认占位人设,后续可手动改。
必改一处(手动注入时):打开 ai_teacher.js,把顶部 const SYSTEM = ... 换成本课程的答疑人设
(课程名、核心知识点、回答口径、红线约束)。示例见 references/ai-teacher.md 第三节。
调用方式(fetch 直连 DeepSeek):
POST https://api.deepseek.com/chat/completions
Authorization: Bearer <sk-...>
{ "model":"deepseek-flash", "messages":[...], "stream":true }
返回标准 SSE(data: {"choices":[{"delta":{"content":"…"}}]} … data:[DONE]),面板已内置解析。
CORS 兜底(关键):file:// 双击打开时直连常被跨域拦截。演示时运行本地代理:
python scripts/ai_proxy.py # 默认读同目录 Deepseek秘钥.txt
再把面板 Base URL 改为 http://localhost:8787(秘钥字段可留空,代理注入)。代理已实测正常。
明细、秘钥文件约定、发布注意见 references/ai-teacher.md。
与本模板兼容:若课件主脚本有全局翻页快捷键,必须在 keydown 监听最前加 INPUT/TEXTAREA
守卫,否则在 AI 输入框打字会被翻页拦截(references/ai-teacher.md 第七节有代码)。
阶段 6 · 发布(按需二选一)
6A · 发布到资料库生成分享链接(推荐,保留版本、可持续编辑)
先压缩音频:python scripts/wav_to_mp3.py --in-dir audio --glob "*.wav" --out-dir audio_mp3
再按 references/library-publish.md 走:开启事务 → 逐个上传 HTML+音频+logo → 提交 →
publish_page.py 同步发布 → 校验发布产物。
6B · 自包含单文件(邮件/离线/第三方托管)
python scripts/build_standalone.py --html 演示.html --out 演示_自包含版.html \
--audio-dir audio_mp3 --pattern "clone_step_{n:02d}.mp3" --count 83 --logo 公司logo.png
脚本清单
| 脚本 | 作用 |
|---|---|
render_ppt.py |
spec.json → 分步配音交互 HTML |
write_script_xlsx.py |
逐字稿 → Excel(行数强校验) |
gen_edge_tts.py |
批量 edge-tts 配音 |
indextts_clone.py |
单条 IndexTTS 声音克隆 |
gen_clone_batch.py |
批量克隆(复用上传、429 退避、断点续传) |
wav_to_mp3.py |
wav → 单声道 mp3(体积降 8~10 倍) |
verify_audio.py |
交付前"每一步都有音频"自检 |
build_standalone.py |
打包成单文件自包含 HTML |
ai_proxy.py |
本地代理,转发大模型请求、规避浏览器 CORS(AI 解答老师用) |
全部脚本用 WorkBuddy 托管的 Python(venv)运行,路径形如
<用户目录>/.workbuddy/binaries/python/envs/default/Scripts/python.exe;
依赖 openpyxl、edge-tts、httpx、imageio-ffmpeg(缺则装进该 venv,禁止全局安装)。
常见坑速查
| 现象 | 真因 | 处理 |
|---|---|---|
| 页面空白 | 内容依赖 JS 揭示,但滚动未触发 | 模板已默认全部显示;存量文件见 references/legacy-ppt-retrofit.md |
| 每页只讲第一句 | 未加连播 | 音频 ended → autoNext()(模板已内置) |
| 发布后没声音 | 分享页是单文档壳,同级资源 404 | 音频必须进页面包,或改自包含版 |
| 音频在 CDN 上无法解码 | CDN 把 .mp3 返回错误 Content-Type |
模板已用 fetch+Blob 显式指定 MIME |
| 克隆脚本报"推理失败" | process_completed 无 success 字段 |
见 references/voice-cloning.md 三个坑 |
| 批量上传 429 | 请求过密 | 串行 + 间隔;参考音频只上传一次 |
| 提交后链接没变 | 没同步发布 | commit 后必须 publish_page.py |
| 首屏底部被截断 | 装饰元素过大/间距过大 | 缩小元素、压缩间距与字号 |
| AI 老师无反应/保存无反馈 | 配置未落盘或无状态提示 | 面板已带状态栏+测试连接;发送前自动 saveCfg;F12 看 Console |
| 直连 DeepSeek 报 CORS / Failed to fetch | file:// 打开被跨域拦截 |
运行 python scripts/ai_proxy.py,Base URL 改 http://localhost:8787 |
| 在 AI 输入框打字触发翻页 | 主脚本全局 keydown 未排除输入框 | keydown 最前加 INPUT/TEXTAREA 守卫(见 ai-teacher.md 第七节) |
| AI 回答与当前页无关 | 未勾选「附带当前课件页」或页文本取不到 | 确认 ctx-toggle 勾选;currentSlideText() 已兼容多数 slide 结构 |
交互能力(模板自带)
- 开始遮罩(满足浏览器自动播放策略)
- 一段讲完自动续播下一步,跨页自动翻页,末步停止
- 控制条:⏮上一页 / ◀上一步 / ⏯暂停 / 🔁重播 / ▶下一步 / ⏭下一页 / 🔊静音 + 进度显示
- 键盘:方向键·空格翻步,Home/End 跳首尾,
P暂停、R重播、M静音 - 右侧圆点跳页;滚轮滑到某页自动激活并续播
- 全页内容常驻可见,当前讲解项金色高亮
- Ctrl+P 可直接导出 PDF(导航控件自动隐藏)