音画同步微课视频

SKILL教育学习
教育学习 未标注权限 许可证不明确
安装这个技能

把「一段讲课音频 + 一份把音频按页对应的讲稿 Word + 一份 PPT」合成一支音画同步的讲课视频,音频讲到哪页切到哪页;用户要求时还能复现 PPT 的逐条动画(每个动画组配线索词,跟着讲解逐条揭示)。触发场景:音频配 PPT 做视频、讲课录音配课件、按讲稿给每页配画面、把录音做成微课视频、音频与 PPT 内容对应、避免音频讲第N页而画面显示第N±1页、视频画面匹配 PPT 动画播放效果。

一句话选型结论

按资料字段整理

音画同步微课视频是教育学习类的 Agent Skill,安装方式未标注,权限级别未标注,许可证不明确,有可直接复制的安装命令。技能本质是给模型读的说明书,效果取决于模型与客户端;装完先用一个小任务验证,再放进正式流程。

分类教育学习 安装方式未标注 权限级别未标注权限 许可证许可证不明确
累计安装 未标注 本周 未标注
GitHub 星标 未标注 仓库 未标注
下载量 未标注 技能市场下载数
权限级别 未标注权限 安装前请确认风险范围
许可证 许可证不明确 未说明

安装

未标注
在支持 Skills 的客户端里执行,装完问一句「你有哪些技能」验证
skillhub install user_2697f411/ppt-narration-video
  1. 1
    确认客户端支持

    确认你的客户端支持 Skills(Claude、Codex、Cursor 等),并升级到较新的版本。

  2. 2
    拿到技能文件

    执行上面的命令安装;也可以把仓库里的 SKILL.md 手动放进客户端的 skills 目录。

  3. 3
    刷新技能列表

    重新打开客户端,或刷新技能列表,让新放进来的技能被加载。

  4. 4
    验证是否生效

    问一句「你有哪些技能」,确认列表里出现它,再拿一个小任务试跑。

技能档案

教育学习
字段来自技能仓库与公开索引,中文说明由本站标注
作者 / 维护方user_2697f411仓库 未标注 · 版本 未标注
宿主客户端未标注技能可被这些客户端加载
安装方式未标注安装包 community
权限级别未标注权限安装前请确认风险范围
许可证未说明许可证不明确,商用前请自行确认条款
仓库可用性available技能仓库当前是否可访问
最近核验2026-11-06上游最后核验时间 · 数据更新 2026-11-06
正文体量41.5 KBSKILL.md 原文大小,越大说明说明书写得越细

典型使用场景

3 类
按分类整理的常见用法,具体以 SKILL.md 为准
讲解知识点

按水平把知识点讲清楚。

出练习题

按章节生成练习与答案。

制定学习计划

按目标和时间排学习节奏。

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 有可直接复制的安装命令,不用自己翻文档找入口。

先想清楚的地方

  • 没有标注权限级别,实际能做什么要装好后确认。
  • 许可证不明确,商用或二次分发前先到仓库确认条款。
  • 没有做站内核验,内容以作者仓库为准。
  • 技能只是说明书,实际效果取决于模型与客户端,装完先用小任务试一遍。

更适合

  • 需要在教育学习场景里给模型补一套做法的人。
  • 愿意先读一遍 SKILL.md、确认它会让模型做什么的人。

不太适合

  • 对许可证要求很严、必须明确可商用的人。
  • 只想装完就自动变强、不打算看说明书的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
技能权限级别安装方式累计安装宿主客户端安装命令
音画同步微课视频当前 未标注权限 未标注 未标注 未标注 有
食谱创建课堂课程 未标注权限 GitHub 仓库 25.5 万 Codex、Claude、Cursor 有
语言学习 联网访问 ClawHub 市场 23.6 万 OpenClaw 有
learning 效率 只读 ClawHub 市场 19.2 万 OpenClaw 有
学习习惯 未标注权限 ClawHub 市场 10.2 万 OpenClaw 有
日语家教 只读 ClawHub 市场 8.2 万 OpenClaw 有

SKILL.md 全文

41.5 KB
模型实际读到的说明书原文,来自技能仓库

讲课音频 × PPT → 音画同步视频

用户给出三样东西:讲课音频、按页对应讲稿的 Word、PPT。
要求产出视频,且画面必须严格跟着讲稿/音频走。

> 默认交付形态 = 整页静态展示(每页 PPT 整页图,音频讲到该页就切到该页)。
> 曾有一次用户实测后否决了「复现 PPT 逐条动画」:「这个播放的动画不好匹配不上,
> 直接让每页 ppt 展示对应音频就行了」——所以不要主动做。
>
> 但用户明确要求时要做,而且现在这条路已经便宜了:2026-09-30 那两轮用户都说
> 「视频画面最好能匹配 ppt 中的动画播放的效果」,走附录路径做出两版:
> 15 页 / 56 单元 / 71 帧(翻页 14/14、页内揭示 56/56),
> 17 页 / 65 单元 / 82 帧(翻页 16/16、页内揭示 64/64)。帧生成每帧 0.1–0.6 s。
> 成本高的是当年的「图像遮掩法」,现在换成 COM 直接隐藏形状(见附录)后,
> 这条路已经不比整页静态贵多少。判断规则:用户提了动画就做,没提就整页静态。
>
> ⚠️ 画面匹配动画时,最容易犯的错不是"动画不对",而是"揭示太早" ——
> 整页一步到位地把所有条目铺出来,用户会立刻发现"我在讲第一条,后面几条已经在了"。
> 详见「揭示时刻怎么定」一节的铁律。

工作目录怎么放(先读这一节)

视频工程是重资产:整页帧 PNG 最占地方,一个 20 页 / 56 帧的工程约 2 GB,
ASR 模型又要几百 MB。所以开工前先把工作目录定在磁盘空间充足的分区,不要塞进系统盘。

export PPTVID_ROOT="/d/aiwork/vid-<任务名>"     # Git Bash / macOS / Linux
export PPTVID_ROOT="D:\aiwork\vid-<任务名>"     # Windows 原生路径也可以
  • 这个变量就是所有脚本的根目录,脚本里的 ROOT 统一从它读取;
    不设时默认取 scripts/../work。
  • 模型缓存同理,别落在系统盘:export HF_HOME="$PPTVID_ROOT/models"。
  • 交付成品放到用户指定位置(桌面或项目目录),不要在工作目录里另存多份副本。

脚本的可移植改造(已在本包里做好)

脚本里的七处硬编码已经全部换成环境变量,换机器不用改代码:

环境变量 作用 默认值
PPTVID_ROOT 工程根目录(音频、JSON、帧、成片都在这里) scripts/../work
FFMPEG / FFPROBE ffmpeg 可执行文件 ffmpeg / ffprobe(走 PATH)
PPTVID_MODELS faster-whisper 模型下载目录 $PPTVID_ROOT/models
HF_ENDPOINT 国内加速镜像 https://hf-mirror.com

先做三件事,再动手

1. 探测「音频」真实格式 —— 别信扩展名

实测最多的坑:文件名 音频.mp3,实际是 MP4(HEVC 视频 + AAC 音频),
甚至可能是手机录屏(竖屏 1320×2868),画面是聊天记录/桌面,不是 PPT。

FF="$FFDIR/ffmpeg.exe"
"$FFDIR/ffprobe.exe" -v error -show_format -show_streams "$AUDIO" \
  | grep -E "^(codec_type|codec_name|width|height|duration|sample_rate)="
  • 出现两条 codec_name(hevc + aac)→ 是视频不是音频。
  • 若视频内容与 PPT 无关(录屏),只取音轨,画面一律用 PPT 渲染。
  • 纯 Python 数 MP3 帧会得到完全错误的时长(实测 25min vs 真实 6:57)——必须用 ffprobe。

2. 取时长与音轨

"$FFDIR/ffmpeg.exe" -v error -i "$AUDIO" -vn -ac 1 -ar 16000 -c:a pcm_s16le audio.wav -y
"$FFDIR/ffmpeg.exe" -v error -i "$AUDIO" -vn -c:a aac -b:a 128k audio.m4a -y

3. 读 Word 的页标记

文档形如 第1页ppt + 正文、第2页ppt + 正文……用 python-docx 逐段扫 第(\d+)页。
每页段落就是那页的口语讲稿。

核心:音频切页对齐

没有时间戳可用(Word 里通常没有任何 mm:ss),音频也没有足够明显的长静音
(实测整段连续讲话,silencedetect -32dB:d=0.8 只在首尾各命中一次)。
所以必须做语音识别,再把每页讲稿的开头句在 ASR 里定位。

步骤

a) 转写(faster-whisper,CPU int8 即可)

from faster_whisper import WhisperModel
m = WhisperModel('small', device='cpu', compute_type='int8', download_root=r'C:\animwork\models')
segs, info = m.transcribe('audio.wav', language='zh', beam_size=5,
                          vad_filter=True, vad_parameters=dict(min_silence_duration_ms=400))
# 存 [{start, end, text}] 到 json

> whisper 常输出繁体,且同音字错(新授→新售、教案→交案、梳理→书里;
> 本轮实测还有:用词→用时、齐答→其达、骨架→股价、例题→立题、实物→食物、板书→版书)。
> 但「归一化到位」之后,精确子串比模糊匹配更好(见下 b)。

b) 对齐每页
- 归一化三步,缺一不可:
1. opencc t2s 转简体;
2. 阿拉伯数字 → 中文数字(re.sub(r'\d+', num2cn, s))。
⚠️ 这一步最容易漏,漏了就大面积匹配失败:讲稿写「一节 40 分钟的课」,
whisper 转出来是「一节四十分钟的课」,不转换则锚句永远找不到。
需自己实现 num2cn(支持 0–99;「10」→「十」、「40」→「四十」、「15」→「十五」)。
3. 只保留 CJK 汉字('\u4e00' <= ch <= '\u9fff'),剔除标点/空格/英文。
- 匹配顺序:先精确子串,后模糊。
① 分段顺序校验(首选,第二轮实测 17/17 全中):
把锚句按 。;?! 切成若干段,每段取前 L 字(L = 12,10,8,6,5,4 由长到短),
要求各段匹配点依次递增(prev = idx + L 只往后找),全通过才采纳第 1 段的匹配点。
→ 这一步专治锚句首段在音频里出现两次(正文里又提了一遍)导致的长匹配被抢先命中。
② 前缀子串兜底:长度由长到短试 (16,14,12,10,8,6,5,4),在 full[cursor:] 里 str.find。
只有全部失败才退到 difflib.SequenceMatcher 滑窗(窗口 min(len(anchor),24),
搜索范围 cursor .. cursor+1400,阈值 0.70)。
- 锚句可以微调为 ASR 的实际用字(只改字、不改位置):比如讲稿写「新授课」而音频被
whisper 听成「新售课」、「唤醒」听成「患性」,把 PAGES 里那 2 个字跟着改,
精确匹配立刻命中。时间点不受影响,因为锚句只是用来定位「这段话说在哪一刻」。
- 游标单调:每页匹配成功后 cursor = idx + 1,防止后续页匹配到前面去。
- 实测:15 页 / 275s → 14 页 exact16(1 页 fuzzy 0.958);
17 页 / 416s → 分段顺序校验 + 用字微调后 17/17 全部 exact,相似度 1.000。
→ 归一化做对了,这个问题几乎是"免费"的;不要一上来就上模糊匹配,慢且易跳错页。
判读标准:只要有一页掉到 fuzzy 且 ratio < 0.95,就该去查锚句用字,别急着人工挪时间。
- 字符位 → 时间:按所在 ASR 段内的比例插值(st + (en-st)*frac),比取段首更准。

c) 吸附到自然停顿(避免切在词中间)
算 20ms 帧 RMS → dB → 噪声底取 20 分位 → 阈值 floor+11dB → 找 ≥0.18s 的停顿区间,
把页起点吸附到最近停顿中点(容忍 1.2s),再吸附到最近 ASR 分段起点(容忍 0.35s)。
> 实测 17 页全部本来就在停顿点上,delta 全 0 —— 说明 ASR 分段边界本身就是好切点。

d) 收尾:end[i] = start[i+1],末页 end = 音频总长 - 0.25s。
校对:各页时长之和必须 ≈ 音频总长(实测 411.8s vs 416.57s,差的是首尾静音)。

组装(默认路径:整页静态)

⚠️ 不要用 -f concat demuxer + Windows 写的清单文件 —— 会静默丢页。
实测:concat_plain.txt 里每行以 CRLF 结尾时,ffmpeg 解析 duration 7.000\r 失败,
于是把整份清单重复读取了一遍(frame=18 = 17 张 + 末帧重复)。症状极具迷惑性:
- 视频总时长看起来正常(因为它把自己拼了 2 遍、每页时长也照用了一遍)
- 但中间若干页永久消失,而且后段会从中间某个页码重新开始播
- 实测表现为 50s→66s 从第 4 页直接跳到第 5 页,第 6、7 页凭空不见,
用 1 fps 抽帧指纹扫描才发现(第 8 页出现在 128s 而非 140s)

正确做法:用 concat 滤镜(filter_complex),完全不碰清单文件解析。

inputs, filt, labels = [], [], []
for k, (pg, d) in enumerate(segs):
    inputs += [&#x27;-loop&#x27;, &#x27;1&#x27;, &#x27;-framerate&#x27;, &#x27;30&#x27;, &#x27;-t&#x27;, f&#x27;{d:.4f}&#x27;, &#x27;-i&#x27;, rf&#x27;{SL}\幻灯片{pg}.PNG&#x27;]
    # ⚠️ scale + setsar 都必须有,见下方 SAR 坑
    filt.append(f&#x27;[{k}:v]scale=1920:1080,setsar=1[v{k}]&#x27;)
    labels.append(f&#x27;[v{k}]&#x27;)
filt.append(&#x27;&#x27;.join(labels) + f&#x27;concat=n={len(segs)}:v=1:a=0[outv]&#x27;)
# 滤镜串长时写入文件,用 -filter_complex_script 传入,避免命令行长度限制
open(&#x27;filter.txt&#x27;,&#x27;w&#x27;,encoding=&#x27;utf-8&#x27;,newline=&#x27;\n&#x27;).write(&#x27;;\n&#x27;.join(filt) + &#x27;\n&#x27;)
&quot;$FF&quot; -y &lt;每帧 -loop 1 -framerate 30 -t d -i frame.png ...&gt; -i audio.m4a \
  -filter_complex_script filter.txt -map &#x27;[outv]&#x27; -map &#x27;&lt;N&gt;:a:0&#x27; \
  -c:v libx264 -preset medium -crf 20 -pix_fmt yuv420p -r 30 \
  -c:a aac -b:a 192k -movflags +faststart -shortest out.mp4

-loop 1 -t d 逐帧定时长,一条命令直出成片,无中间文件、无解析歧义。

> ⚠️⚠️ 但段数多时会爆内存(2026-09-30 实测,必踩):
> 单次 -filter_complex concat 会把所有 1080p 输入同时解码进内存。
> 70 段时稳定报
> [fc#0] Task finished with error code: -12 (Cannot allocate memory) → Conversion failed!。
> 症状:输出文件被截断但仍存在(11.4 MB vs 应有的 16 MB),极易被误判为成功。
> → 段数 ≳ 50 时改用 concat demuxer 两段式(流式读,内存与段数无关):
> python &gt; # ① 每帧先压成一段静音小片 &gt; for i, s in enumerate(segs): &gt; subprocess.run([FF,&#x27;-y&#x27;,&#x27;-loglevel&#x27;,&#x27;error&#x27;, &gt; &#x27;-loop&#x27;,&#x27;1&#x27;,&#x27;-framerate&#x27;,&#x27;30&#x27;,&#x27;-t&#x27;,&#x27;%.4f&#x27;%s[&#x27;dur&#x27;],&#x27;-i&#x27;,s[&#x27;file&#x27;], &gt; &#x27;-vf&#x27;,&#x27;scale=1920:1080,setsar=1&#x27;, &gt; &#x27;-c:v&#x27;,&#x27;libx264&#x27;,&#x27;-preset&#x27;,&#x27;ultrafast&#x27;,&#x27;-crf&#x27;,&#x27;18&#x27;, &gt; &#x27;-pix_fmt&#x27;,&#x27;yuv420p&#x27;,&#x27;-r&#x27;,&#x27;30&#x27;,&#x27;-an&#x27;, clip_i]) &gt; # ② 清单必须 LF 结尾(用 newline=&#x27;\n&#x27; 写),路径用正斜杠 &gt; open(lst,&#x27;w&#x27;,encoding=&#x27;utf-8&#x27;,newline=&#x27;\n&#x27;).write(...) &gt; # ③ 串片 + mux 音轨 &gt; subprocess.run([FF,&#x27;-y&#x27;,&#x27;-loglevel&#x27;,&#x27;error&#x27;,&#x27;-f&#x27;,&#x27;concat&#x27;,&#x27;-safe&#x27;,&#x27;0&#x27;,&#x27;-i&#x27;,lst, &gt; &#x27;-i&#x27;,AUD,&#x27;-map&#x27;,&#x27;0:v&#x27;,&#x27;-map&#x27;,&#x27;1:a&#x27;, &gt; &#x27;-c:v&#x27;,&#x27;libx264&#x27;,&#x27;-preset&#x27;,&#x27;medium&#x27;,&#x27;-crf&#x27;,&#x27;20&#x27;,&#x27;-pix_fmt&#x27;,&#x27;yuv420p&#x27;,&#x27;-r&#x27;,&#x27;30&#x27;, &gt; &#x27;-c:a&#x27;,&#x27;aac&#x27;,&#x27;-b:a&#x27;,&#x27;192k&#x27;,&#x27;-movflags&#x27;,&#x27;+faststart&#x27;,&#x27;-shortest&#x27;, out]) &gt;
> 注意:这里能用 demuxer 是因为小片是 ffmpeg 自己产的、时长已烧进容器,
> 不再依赖清单里的 duration 行 —— 与上面「不要用 demuxer + 手写 duration」不矛盾。
> 判据:ffmpeg rc == 0 且 文件大小合理;只看文件存在会被截断产物骗过。

> ⚠️⚠️ SAR 坑(本轮实测,必踩):PNG 经 image2 进来后 ffmpeg 会给它一个
> 字面不同的 SAR —— 报错形如
> Input link in0:v0 parameters (size 1920x1080, SAR 1:1) do not match the corresponding &gt; output link in0:v0 parameters (1920x1080, SAR 5669:5669)。
> 5669:5669 数值上就是 1:1,但字面不同,concat 直接拒绝并 Failed to configure output pad。
> → 每一路输入都要 scale=W:H,setsar=1(只写 setsar=1 也行,但显式 unity scale 更稳)。
> 这条与"源是 PNG 还是 PPT 导出图"无关,只要用 concat 滤镜就加上,零成本。

> 若坚持用 demuxer,必须先 open(f,&#x27;w&#x27;,encoding=&#x27;utf-8&#x27;,newline=&#x27;\n&#x27;) 写清单,
> 但即便写对也可能因 sub-second duration 累积漂移 —— 不如直接用滤镜。

时长必须裁齐

滤镜直出的时长会略短于音频(实测 416.33 vs 416.56)。用 -t &lt;音频精确秒数&gt; 配
-c:v copy -c:a aac 裁齐,不要重新编码视频:

&quot;$FF&quot; -y -i out.mp4 -t 416.556667 -c:v copy -c:a aac -b:a 128k out_t.mp4

720p 从 1080p 成品转

&quot;$FF&quot; -y -i out_t.mp4 -vf scale=1280:720 -c:v libx264 -preset medium -crf 21 -c:a copy out_720.mp4

⚠️ 不要从 concat 源 + -preset slow 转 —— 实测时长漂到 427s。

交付前必做的两道复核

这两道复核是必须的,不是可选的 —— concat / 对齐环节的错会静默通过,只有扫描能发现。

  1. 逐页中点抽帧:按 timeline 取每页时间中点抽帧,与参考图逐一算
    灰度差,确认每页自己都是最近邻,且与次优解的差距是量级式的(实测 0.1–1.2
    vs 7 以上),否则说明该时刻是对的但页库有重复项。
  2. 全程指纹扫描(反查最近邻) —— 这是最有价值的一道,建议做成"双向验证":
    - 抽出全部参考帧(整页版=每页 1 张;动画版=每帧 1 张,本轮 71 张);
    - 把成片按 0.2 s 采样(不是 1 fps —— 粒度粗了看不出页内揭示的偏差),
    缩放 48×27 灰度,与每张参考帧算 L1 均值,取 argmin → 反推
    「这一刻画面属于哪一页、第几步」;
    - ffmpeg -vf fps=5,scale=48:27 -pix_fmt gray -f rawvideo - 直接 pipe 进 numpy,
    比落盘成 PNG 快得多;距离矩阵按 200 行一批做广播,避免内存爆掉;
    - 两个检查项:
    a) 翻页时刻:页码序列的变化点必须 = 页数−1,且与计划起点偏差 ≤0.8 s;
    b) 页内揭示时刻:每页每个 step 首次出现的时刻 vs reveal_plan,偏差 ≤1.0 s。
    - 实测(15 页 / 56 步 / 275 s):翻页 14/14 全中、页内揭示 0/56 超差,
    最大偏差 0.25 s —— 恰好就是 0.2 s 采样粒度的量化上限,说明对齐其实是精确的。
    - 匹配距离本身也是画质指标:mean≈2.2 / max≈2.6(0–255 尺度)说明
    成片与无损参考帧几乎无差,CRF 20 足够。

> 上一版就是跳过第 2 步直接交付,导致缺 2 页的视频交到用户手上。
> 只要页数对不上,或出现「某页计划时刻没有对应检出」,一律视为未通过。

附录:复现 PPT 逐条动画(默认不用,仅当用户明确要求)

> 用户实测后否决了这条路径。以下内容保留备查,不要在没被要求时启用。

提取动画的「揭示分组」

遍历 ppt/slides/slideN.xml 的 &lt;p:timing&gt;,按文档顺序收集带 presetID 的
效果节点,取 nodeType 与其 spTgt/@spid:

  • clickEffect → 开新组
  • withEffect → 并入当前组(同时出现)
  • afterEffect → 开新组(自动接续)

> ⚠️ 别用 mainSeq 下顶层 par 数来分组 —— 实测每组自己套了 par,
> 顶层数 ≠ 组数(会得到 1)。按文档顺序 + nodeType 划分才得到正确的
> 3/5/3/5/2/3/4/2/4/6/2/3/4/2/6/9/2 组(与每页 click 数吻合)。

生成「逐组揭示」帧 —— 首选:COM 直接隐藏形状

✅ 这是本轮的实证结论,比下面那套图像处理法简单一个数量级、且完全无残影。

核心事实:PowerPoint COM 的 Shape.Id 与 slide XML 里 spTgt/@spid 是同一个数。
→ 可以直接拿 reveal_groups 提取出的 spid 列表去开关形状,不需要任何坐标计算。

# 前置:Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force
# 打开时不 Save,所以 ReadOnly 随便;Visible=1 更稳
$ppt = New-Object -ComObject PowerPoint.Application
$ppt.Visible = 1
$pres = $ppt.Presentations.Open(&#x27;C:\ascii\path\deck.pptx&#x27;, $true, $false, $true)

# 逐帧:把「尚未揭示」的形状 Visible=0,再整页导出
foreach ($sid in $job.all) {
    $sh = $shCache[[string]$sid]              # 进页时先建 id-&gt;shape 缓存,别每帧重扫
    if ($job.revealed -contains [string]$sid) { $sh.Visible = -1 } else { $sh.Visible = 0 }
}
$curSlide.Export($outPath, &#x27;PNG&#x27;, 1920, 1080)  # 单页导出到文件

要点与实测:
- Slide.Export(文件路径,&#x27;PNG&#x27;,1920,1080) 是单页导出到指定文件;
Presentation.Export(目录,...) 是整册导出到目录(命名 幻灯片N.PNG),两者别混。
- 不需要 Select()、也不需要设 ViewType。上一轮踩的"NotesPage 视图下 Export 失焦"
只出现在 Presentation.Export;Slide.Export 在 Application 不可见/未选中页时同样正确。
- 速度极快:91 帧实测每帧 0.1–2.0 s(多为 0.1 s),全程十几秒 —— 不必后台跑。
- 增量切换:进页时把该页全形状 Visible=1,页内逐步只改变动的那几个 id。
- 退出前把用过的页 Visible 复原(或直接 Close() 不保存,反正 ReadOnly)。
- 帧大小逐帧递增(如 P4 的 189K→203K→220K→222K→229K)是正常且是好兆头,
可当快速自检;某帧突然变小 = 该帧藏多了。

⚠️ 动画目标可能落在组合 grpSp 内部 —— 那种 spid 在顶层 Shapes 里找不到,需要
GroupItems 递归。动手前先核对一遍:从 shape_info 里检查所有用到的 spid 是否都在顶层
(本轮 129 个 spid 全在顶层,0 例外)。

备选(COM 不可用时):图像遮掩法

每页导出整页 PNG 后,为每组生成一帧:把「尚未出现」的组的区域,用「静态底图」同位置内容盖掉。
静态底图 = 整页图,但把所有动画形状区域填成邻近背景色。
遮盖时必须用实测墨迹包围盒,不能只信 XML 声明框:

# 文字常溢出声明框(垂直锚点导致),实测同一形状
# 声明框 y=805..911,真实墨迹 y=747..878 —— 差 58px!
# 做法:在声明框外扩 26px 的邻域内找 &lt;170 的深色像素,取其包围盒,再外扩 8px
sub = gray[y0:y1, x0:x1]
ys, xs = np.where(sub &lt; 170)

症状对照:只信声明框 → 画面残留淡淡文字鬼影("残影像素数 37187");
改用墨迹包围盒 → 干净(残影 0)。

掩盖用「静态底图粘贴」而非纯色填充

纯色填充在米白渐变底上会留色块。正确做法:先造静态底图(只含静态元素),
再把未出现组的矩形区域从静态底图 crop 出来 paste 回去。

⚠️ 用户可能给两份页序不同的 pptx

本轮实测:用户同时给了「一节课怎么观察 结构化观察法 无动画最终.pptx」和
「…… 最终.pptx」,两份第 13 / 14 页互换(内容一致,只是顺序不同)。
- 画面以「无动画最终」为准(它与 Word 讲稿的页码一致,是用户自己校准过的)。
- 动画分组从「最终」提取,提取时要 PAGE_MAP = {13:14, 14:13} 换回来。
- 动手前先核对两份的形状 id 集合是否逐页一致(本轮 0 页不一致),
一致才能安全套用动画分组。

「页 × 步骤」拼接(附录内)

# 时长规则(页与页无缝,本轮定稿):
#   P1  step0 起点 = 0
#   Pn  step0 起点 = P(n-1) 的 end   &lt;- 音频开始讲本页的那一刻翻页
#   Pn  step_k 起点 = units[k-1].reveal
#   最后一帧末端 = 音频总长(保住音频尾巴,不要被 -shortest 砍掉)
bounds = [prev_end] + [u[&#x27;reveal&#x27;] for u in units] + [page_end]

必须丢掉零长帧:当某页首个单元的 reveal 恰好 = 该页起点时,step0 区间长度为 0,
要去掉(本轮 71 帧 → 实际 70 段)。否则 ffmpeg 会得到一个 0 秒输入。
> 拼接同样用上面的 concat 滤镜(含 setsar=1),不要用 -f concat demuxer。
> 把每帧当成一个 -loop 1 -t d -i frame.png 输入即可。

揭示时刻怎么定

⚠️ 别用形状自身文本当线索 —— 实测会大面积误配:
P1 副标题被推到页尾(3.13→9.03 s)、P2 两组挤在页首、P3 两组只差 0.9 s。
原因:形状文案(书面语)与讲师口播(口语)是两套表述,能对上只是巧合。

正确做法:人工写线索词 CUE,按该音频 ASR 转简体后的实际用字书写
(用来绕开 whisper 的同音字错,例如把线索写成「其答」而不是「齐答」)。

> ⚠️⚠️ 铁律:每一组动画都要配线索词,不许用节奏填充代替(2026-09-30 用户当场退回)
>
> 症状:用户反馈「讲评课三项要点这一页,我在讲错误归类,后面的找出根因和跟进巩固
> 都已经出来了」「新授课顺着这三步看下去也是同样的问题,好多 PPT 都是这样子」。
>
> 根因:把「每页都给线索」简化成了「部分给线索、其余靠节奏填充」,而
> STEP_MAX = 2.4s 会把整页所有步压缩在页首 5 秒内全部铺完。
> 实测 P13:讲稿从 294.70 讲到 330.90(36 秒)分三条讲,画面却在
> 292.60 / 295.00 / 297.40 三秒内全出来了 → 讲第一条时后两条已在屏幕上。
>
> 判据(自查):对每一页算 末步时刻 - 首步时刻。若远小于该页音频时长
> (例如页面 48.7s 却只用 5s 就揭示完),几乎必然不同步 → 必须补线索词。
> 正确结果应像 P13:292.30 / 294.99 / 311.07 / 323.47,跟着讲解逐条推进。
>
> 另外:不许把多组归并成一步。P10/P15/P16 曾把 [[0,1],[2,3],[4,5]] 归并,
> 导致「先念标题再解释」的节奏里标题和解释一起出现。PPT 里是一组就一步
> (UNITS[pg] = [[0],[1],...,[n-1]]),归并只对真正同时出现的元件做(同一个卡片内的
> 多行文字,已由 reveal_groups.json 的分组表达,不要再手动合并)。

只给确实没有台词的单元(页尾装饰、纯图形收束)留节奏填充:

MIN_GAP = 0.9    # 相邻揭示最小间隔
LEAD    = 0.30   # 首个单元距页起点
TAIL    = 0.45   # 末个单元距页尾
STEP_MIN, STEP_MAX = 0.7, 2.4   # 无线索时均匀铺开,步长夹在这两者之间

最后强制单调递增 + 不越界。校验必须跑:打印每页单位数、是否单调、是否越界。

线索词命中率要盯:本轮 65 步里 55 步走 cue、仅 10 步走 fill。
若 fill 占比超过三成,说明线索给少了,回去补。

关键概念:归并成「视觉单元」 —— 指把同一时刻一起出现的形状视为一个单元
(同一行标题+副标题、同一卡片的多行文字、表格的表头一行)。
但不要把讲稿会分别点名的条目合并:三条并列要点、三对「现象→课型」,
讲稿会逐条念,就必须逐条揭示。

对结构特殊的页,直接手工给绝对时刻。

🔴 帧下标错位:改时长算法时最容易踩的坑(2026-09-30 实测)

帧语义必须先说清:pNN_s0 = 全隐藏(空页);pNN_sK = 已揭示 units[0..K-1]。

错误写法(会整体错位一格,症状是"每步都晚约一个步长"):

bounds = [prev_end] + [u[&#x27;reveal&#x27;] for u in units] + [t1]   # ← prev_end 与 reveal[0] 重复
for k in range(len(units)+1):   # 段 k 配帧 sK

当 units[0].reveal == t0 == prev_end(该页首句台词即首句讲稿时必然如此)时,
bounds 前两项相同 → s0 段零长,但代码仍把 s1 配到第 2 段,
于是 s1 出现在 units[1].reveal 而不是 units[0].reveal,全页错位。

正确写法:

starts = [t0] + [u[&#x27;reveal&#x27;] for u in units]        # 段 k 的起点
ends   = [u[&#x27;reveal&#x27;] for u in units] + [t1]        # 段 k 的终点
for k in range(len(starts)):
    s, e = starts[k], ends[k]
    if k == 0 and s &gt; prev_end + 0.02: s = prev_end   # 页首空档用 s0 补
    if e - s &lt;= 0.02: continue                        # 零长段连同其帧一起丢
    segs.append({&#x27;step&#x27;: k, ...})                     # 段 k ↔ 帧 sK

自检:修完必须跑 verify_sync.py,看「页内揭示步切换」的 dev 列。
若每一步的 dev 都约等于前一步的时长(等差正偏移),就是本坑;
若 dev 都在 ±0.2s 内,才算过。

校验脚本自身也会错:核对期望值的下标

verify_sync.py 里 exp = un[k - 1] 用的是「帧 sK 由 units[k-1] 触发」,
这是对的。但若前面改了 UNITS 的结构,务必重新确认这层对应关系 ——
本轮曾因帧下标错位,误以为是校验脚本错,白排查一轮。
判据:拿一页手工算(P2:units[0].reveal=7.50,则 s1 应在 7.50 出现;
若实测 18.00 = units[1].reveal,就是帧错位,不是校验错)。

交付

  • 输出 1080p + 720p 两份(-crf 20 / -crf 21,音频 -c:a copy 从 1080p 转 720p)。
  • 附一张逐页总览拼图(每页取"完全展开"那一帧,栅格 + 细边框),
    用户可一眼核对「哪一页配哪一段」。本轮命名
    一节课怎么观察-结构化观察法-音画同步-逐页总览.png。
  • 附一份 视频对应关系表.md,含五节:成片信息 / 逐页对应表(页码·主题·出现·结束·停留·讲稿首句)
    / 页内逐条揭示时刻 / 校验结果 / 校验方法。
    整页版不要带「动画组数」列(不再复现动画,该列无意义)。
  • 该表的"页面主题"列从 slide XML 里自动提取,不要凭记忆手写 ——
    本轮手写版把 P13 写成「时间:最常见的记录数据」,实际是「时间·最客观的记录数据」。
    提取规则见附录 C8(两级:先 y∈[15,60] 取最大高度,再退化到上部最大高度)。
  • 另附一张「成片抽帧核对带」:从编码后的 MP4 本体均匀抽 24 帧拼图,每格标注
    t=… P… 第…步。这道复核和「逐页总览」不重复 —— 总览用的是 COM 导出的无损参考帧,
    只有这道能证明编码之后的画面仍与计划一致(顺带肉眼扫一遍有没有花屏/丢页)。
    同时跑一遍 ffmpeg -i out.mp4 -f null -(无输出 = 流无损坏)并核对
    nb_read_frames == 时长×帧率、音频流时长 == 源音轨时长(证明音轨没被裁)。
  • PPT 导出的 PNG 命名 幻灯片1.PNG 与真实页码的对应关系要先验证
    (用灰度差确认两两不同),不能想当然认为编号=页码。

环境准备

Python 3.10+,安装依赖:

pip install -r requirements.txt
# 等价于:python-docx pillow numpy faster-whisper opencc-python-reimplemented

ffmpeg / ffprobe:装好并放进 PATH(或设 FFMPEG / FFPROBE 指向可执行文件)。
Windows 可直接下 GyanD 的 essentials build:

curl -sL -o ff.zip &quot;https://github.com/GyanD/codexffmpeg/releases/download/7.1/ffmpeg-7.1-essentials_build.zip&quot;
unzip -q ff.zip   # → ffmpeg-7.1-essentials_build/bin/{ffmpeg,ffprobe}.exe

复现 PPT 逐条动画还需要:Windows + 已装 Microsoft Office(走 COM 控制形状可见性),
且 PowerPoint 需允许宏/自动化调用。只做整页静态版则不需要 Office。

> ⚠️ 推理耗时:small 模型转写 275 s 音频实测 2 分 14 秒(CPU int8),
> 放后台跑,别在前台等。

本机 PowerShell 的两个硬限制(写脚本前必读)

  1. .ps1 默认禁跑 → 命令前加 Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force。
  2. Remove-Item 被安全策略拦截(报 [safe-delete][SAFE_DELETE_FAIL_CLOSED])
    → 脚本里不要写删除逻辑。Export 会直接覆盖同名文件,不需要预删。
  3. 脚本路径与内容都保持纯 ASCII(中文被 PS 5.1 按 ANSI 解析会静默中止)。
    C:\animwork\vid\ 这类 ASCII 工作区最稳;源素材(中文名)先 cp 进来。

脚本索引(scripts/)

15 个脚本按流程顺序排列,全部可独立运行。所有路径统一由 PPTVID_ROOT 决定,
换机器/换活只改环境变量与三处业务常量,不动代码。

脚本 作用
read_script.py 读讲稿 Word,按「第N页 ppt」切分(兼容「第五页」中文数字与正文误判)
asr.py faster-whisper 转写 → asr.json(CPU int8,vad_filter 开)
align_pages.py 页锚句对齐:每页讲稿首句在 ASR 里定位,得 page_anchors.json
timeline.py 由锚点生成每页起止时间 → timeline.json(首帧强制 0,末页留 0.25 s)
extract_reveal.py 解析 p:timing 得动画揭示分组 → reveal_groups.json + shape_info.json
probe_anim.ps1 COM 读真值核对(TriggerType),校验 XML 解析出的组序
make_groups.py 合并 XML 分组与 COM 真值 → 最终 reveal_groups.json
plan_reveal.py 计划各单元揭示时刻(CUES / NO_MONO / MERGE / PRESTAGE)→ reveal_plan.json
make_jobs.py 摊平成 jobs.json(每页步数+1 帧,只存 hide 列表)
export_frames.ps1 COM 逐帧导出(隐藏未揭示形状)→ frames/f001.png…
build_video.py concat 拼帧 + mux 音轨 → 1080p 成片(两段式,防内存爆)
verify_sync.py 反查最近邻验收(翻页 + 页内揭示双向扫描)→ verify_result.json
make_report.py 生成《视频对应关系表.md》(每段讲稿 ↔ 画面时刻)
make_overview.py 逐页总览图(取每页最后一步 = 完全展开帧)
make_strip.py 成片抽帧核对带(横向拼时间轴缩略图)

标准执行顺序

read_script → asr → align_pages → timeline → extract_reveal → probe_anim
→ make_groups → plan_reveal → make_jobs → export_frames → build_video
→ verify_sync → make_report / make_overview / make_strip

换活清单(只改这三处业务常量)

  1. align_pages.py 的 ANCHORS:每页讲稿的首句(20 页就写 20 条),是定页的唯一依据;
  2. plan_reveal.py 的 CUES:每个揭示单元的线索词(讲稿里出现该词即揭示);
    同页还有 NO_MONO(组序≠讲述顺序的页)、MERGE(末步不足 0.8 s 的合并)、
    PRESTAGE(页首骨架前置,防白页);
  3. extract_reveal.py 的 PAGE_MAP:仅当 PPT 页序与讲稿页序不一致时才需要。

素材按固定名放进 PPTVID_ROOT:deck.pptx / script.docx / src.mp4(或 audio.wav)。


附录 C:2026-10-05 实战补遗(《三种课堂记录法》20 页 / 34 步 / 46 帧)

本轮结果:20 页、34 个动画步骤、46 帧;翻页 19/19 全中(最大偏差 0.26 s),
页内揭示 34/34 全中,匹配距离 mean 0.68 / max 0.73。全程顺畅,
但踩到 9 个新坑,换活前先读这一节。

C1. 讲稿 Word 的页码标记可能是中文数字 —— 正则必须两者都认

第1页ppt 与 第一页ppt 会混用(本轮前 9 页是中文数字、第 10 页起才是阿拉伯数字)。
只写 第(\d+)页 会静默漏掉前 9 页,症状是「切出来 11 页 ≠ PPT 的 20 页」。

m = re.match(r&#x27;^第\s*([一二三四五六七八九十\d]+)\s*页\s*[pP][pP][tT]$&#x27;, t)

中文数字要自己转(一..十 / 十一..十九 / 二十..九十九)。

C2. 老 .doc 读不出完整文字 → 先转 .docx

  • python-docx 读不了 OLE2 的 .doc(magic d0 cf 11 e0)。
  • editor_sdk 的 doc_resolve_document_structure 每条 text_preview 上限是
    200 字节(中文只有约 66 字),长段会被截断 —— 只做锚句够用,要全文不够。
  • 首选:Word COM 另存为 .docx(SaveAs2 第二参数 = 12),再 python-docx 全文读。

C3. 「PPT 的真实动画分组」用 COM 读,别只信 XML

Slide.TimeLine.MainSequence + Effect.Timing.TriggerType
(1=onPageClick 开新组 / 2=withPrevious 并入当前组 / 3=afterPrevious 开新组 / 4=onShapeClick)
是放映时实际会怎么播的唯一可靠依据。
本轮 XML 与 COM 两路结果一致(都是 34 组),但工程源文件与用户手上的 pptx 可以完全不同:
工程源里全是 withEffect 且没有 clickEffect,用户版被 PowerPoint 归一化过,
同一页的组数从 10 变成 1。画面一律以用户给的那份 pptx 为准,
动手前先跑一遍 COM 核对分组,不要拿工程文件的结构去推断。
> 注意:打印 TriggerType 时别用字符串键去查 int 值({&#x27;1&#x27;:...}.get(1) 恒取默认),
> 会误以为「读不到触发类型」。分组判定必须用 int。

C4. 🔴 页首空态帧(<0.06 s)必须合并掉

MIN_GAP 会把「首组揭示」推到页起点之后 0.05 s,从而造出一个还没揭示任何内容的 s0 帧。
它只有 1~2 帧画面、没有信息量,却会让校验炸掉:0.2 s 采样网格上它的中点被四舍五入到邻段,
于是「逐页中点抽帧」报 自身距离 20.87(正常约 0.7),「页内揭示」报该帧永远检不出。
修法:时长 ≤0.06 s 的段直接丢弃,把它的起点让给下一段
(raw[i+1][&#x27;start&#x27;] = raw[i][&#x27;start&#x27;])—— 该页便从首个揭示态开始,翻页时刻不受影响。
本轮 46 帧里有 7 个这样的帧,合并后校验全绿。

C5. 封面页首组必须强制 reveal = 0

否则「主标题」要等到第一句话(约 1 s)才出现,视频开头是一秒空白页。
if page == 1: units[0][&#x27;reveal&#x27;] = page_start,并把后续单元顶到 +MIN_GAP 之后。

C6. 🔴 指纹分辨率不够会误报揭示步缺失

48×27 灰度指纹下,细虚线类图形(本轮 P17「三条虚线汇聚」)像素占比极低,
与次优解的 L1 差距只有 0.01,于是该帧永远拿不到 argmin →
报「P17 步1 实测缺失」,看起来像严重不同步,其实画面完全正确。
指纹至少用 128×72。判据:若某步报缺失而「与次优解差距」<0.05,先怀疑分辨率,别急着改时间轴。

C7. 校验脚本自身的下标 bug(比上一轮那个更隐蔽)

d = np.sort(np.abs(samp[i] - ref).mean(axis=(1, 2)))   # 已经排过序
own = d[best[i]]                                       # 却拿 argmin 当下标去取

排序后下标语义已变,own 取到无关值(实测把正常的 0.7 报成 20.9,让人以为还有坏帧)。
先取未排序数组,再排序用于算 gap:

d = np.abs(samp[i] - ref).mean(axis=(1, 2)); ds = np.sort(d)
worst_own = max(worst_own, d[best[i]]); gaps.append(ds[1] - ds[0])

C8. 从 slide XML 自动提取「页面主题」的两级规则

  • 装饰性小标签(本轮 P16 的「教师/学生/时间」、P19 的「一个人去听课」)
    贴在 y=0 且宽度为 0(off-slide);
  • 正文段落框可能比标题框更高(P2 / P13 / P15 的多行正文),单靠「最大高度」会选错;
  • 正确规则:先取 y∈[15,60] 里框高最大的文本框(本 deck 标题固定在 y≈27–36、h≈62),
    没有再退化为「y∈[15,430] 里框高最大」(封面页与过渡页的标题在中部)。

C9. 再次确认的两个既有坑

  • 音频N.mp3 实为 MP4:本轮是 HEVC 886×1920 竖屏 + AAC,329.5 s。只取音轨。
  • PS 5.1 脚本里不能有中文注释:本轮写了带中文注释的 .ps1,
    报 不能对 Null 值表达式调用方法(中文按 ANSI 解析把后面的代码吞了)。
    路径与注释一律纯 ASCII,中文只允许出现在运行时由 COM 取回的数据里。

附录 D:2026-10-20 实战(《四本书里的听评课表设计关键思想》20 页 / 40 步 / 56 帧)

结果:502.53 s,翻页 19/19、页内揭示 40/40 全中(最大偏差 0.11 s),距离 mean 0.96 / max 1.03。
本轮没有踩旧坑,但补上了三个让画面"像一段视频而不是幻灯片放映"的结构性配置 ——
换活时优先看这一节,它们比调时间点更影响观感。

D1. 讲稿页标记还有第三种写法:「第N页」单独一行

同一份 docx 里可以混着三种:第1页ppt、第一页ppt、第五页(就三个字,没有 ppt 后缀)。
更麻烦的是正文段落也会以「第五页」开头(「第五页你做的听评课观察表里……」)。

→ 必须 fullmatch,两种模式都试;不能用 re.match 前缀匹配:

RE_A = re.compile(r&#x27;^第\s*([一二三四五六七八九十\d]+)\s*页\s*([pP][pP][tT])?\s*(对应的逐字稿内容)?$&#x27;)
if RE_A.fullmatch(t): ...

用 re.match 的话,正文那句会被切成一个假页,后续所有页号错位。

D2. 🔴 组序 ≠ 讲稿顺序:加 NO_MONO

症状:某页所有动画组挤在页尾十几秒内全部出现,而讲稿其实是从头讲到尾的。

根因:plan_reveal 默认要求 reveal 随组序号单调递增。但页面上组的排列顺序
和讲稿的讲述顺序可以相反:

页 页面排列 讲稿顺序 后果
P13 五级自评 4分→3分→2分→1分→0分(从上往下) 0分→1分→2分→3分→4分 前 4 组全被 max() 推到末组之后
P18 两种发现 「观察全班得到」在左 先讲案例(右侧),最后才对比左侧 左侧抢先出现
P19 四本书 四张卡 + 中心「互相补充」 先念四本书,最后说「互相补充」 中心圆抢先

→ 做法:NO_MONO = {13, 18, 19},这些页按线索时刻自由排序,不做单调约束;
unit 里存 gids(可能一个 unit 对应多个组),make_jobs 按出现顺序累加隐藏集合:

&#x27;revealed&#x27;: set(s for u in units[:k] for gid in u[&#x27;gids&#x27;] for s in gs[gid][&#x27;spids&#x27;])

帧语义随之变成「已按时间揭示的 k 个组」,不再是「组 1..k」——校验脚本不用改(它只看 jobs 的 start)。

D3. 末步小于 1 秒 → 加 MERGE

症状:P4 只有 7.4 秒音频却排了 3 组,第 3 组(底部结论条)落在 end - TAIL,
只剩 0.45 秒,翻页前闪一下。

判据:末组 reveal 距 页 end < 1 s 就该处理。修法是并组而不是压缩 MIN_GAP:

MERGE = {4: [[0], [1, 2]]}   # 底部那条结论是对「学生学到了什么」的即时收束

并完之后 P4 变成 2 步(35.47 / 37.04),末步有 1.56 秒,正常。
> 只并真正同刻收束的元件;三条并列要点仍要逐条出。

D4. 🔴 页首空页:加 PRESTAGE(最影响观感的一条)

症状:页首若首组揭示时刻离页起点很远,导出的 s0 帧只有 10–13 KB
(整页只剩标题和一条分隔线),视频里 6~7 秒像卡住。

本轮实测:P3 页起点 10.0 / 首组 16.8(6.8 s);P5 38.6 / 44.67(6.1 s);
P19 s0 只有 8 KB。

为什么不能简单地把首组提前:那会抢跑(讲「哪四个维度?」时第一个圆已经在了)。

正确修法:把「容器类」形状从动画组里摘出来,让它们从一开始就在,只让文字随讲稿出现:

PRESTAGE = {
    3:  [&#x27;265&#x27;, &#x27;272&#x27;, &#x27;277&#x27;, &#x27;282&#x27;],               # 四个圆的外框
    5:  [&#x27;336&#x27;, &#x27;335&#x27;, &#x27;341&#x27;, &#x27;340&#x27;, &#x27;346&#x27;, &#x27;345&#x27;], # 三个标签 + 下划线
    19: [&#x27;319&#x27;],                                     # 中心圆
}
# 读原始分组 → 过滤 → 写回 reveal_groups.json,make_jobs 据此算 hide

导出后 P3 的 s0 是「四个空心圆 + 虚线连接」——正好是「待填的四个格子」,
第一组出现时第一个圆变实心 + 文字,视觉引导从左到右。

> ⚠️ 首个圆是「外框 + 实心填充」两层时,只前置外框(本 deck 是 265 外圈 / 267 实心)。
> 两层都前置 → 页首就是一颗「已经高亮但没字」的空圆,比空白页更怪。

⚠️ 骨架前置会改写 reveal_groups.json,必须先存一份 reveal_groups_raw.json,
每次从原始版出发;否则重跑一次就再删一遍(第二次删空组,帧数变少)。

D5. 56 段用两段式,2 分 12 秒出片

段数 56 已超过「≳50 就该换两段式」的线。每帧压成 -preset ultrafast -crf 18 静音小片,
再 concat demuxer 串联 + mux 音轨,全程无内存告警。不要试图一条 filter_complex 拼 56 路。

D6. 标题自动提取的 y 范围必须按 deck 调

原规则 collect(15, 60)(照 2026-10-05 那份 deck 写的)在本 deck 全部落空(标题 y=7–14),
退化到「上部最大」后抓到了图里的「明确目标、重难点」「不仅自己用得好,还能带动其他老师」。

→ 改成 collect(0, 100) or collect(0, 430),并把宽度阈值从 200 降到 150,
20 页里 19 页正确。剩下 P15 页顶只有「第四本」这种眉标,用 MANUAL = {15: &#x27;《学习共同体:走向深度学习》&#x27;} 覆盖。
判断方法:报告生成后逐行扫一遍「页面主题」列,出现"图内文字"或"半句话"就是规则没适配。

常见问题

5 问
答案由本站依据条目字段整理
音画同步微课视频怎么安装?

在支持 Skills 的客户端里执行:skillhub install user_2697f411/ppt-narration-video。安装方式为未标注。装完刷新技能列表或重开客户端。

音画同步微课视频支持哪些客户端?

资料没有标注宿主客户端,但只要客户端支持 Skills(Claude、Codex、Cursor 等),一般都能加载。

音画同步微课视频权限高吗,安全吗?

权限级别为未标注权限。安装前先看它会读取或写入哪些内容。

音画同步微课视频收费吗?

本站不代收费用,也没有标注具体价格。是否收费取决于技能本身和所用的模型或客户端,正式使用前按作者说明确认。

音画同步微课视频用什么许可证?

许可证标注为 未说明(许可证不明确)。商用或二次分发前请到仓库确认条款原文。

热门专题

按场景挑技能
全部专题 →

技能内容以作者仓库为准;分类、权限级别与许可证状态的中文说明为中文整理,最近核验 2026-11-06。