2
0
0

把一份技术研究,做成一集 4 分钟的讲解视频

2026-11-02
2026-11-02
文章摘要
|

把一份技术研究,做成一集 4 分钟的讲解视频

前几天我想弄明白 RAGFlow 到底是什么,顺手做了个实验:不只写一篇研究文档,还要把它做成一条能发给非技术同事看的讲解视频。

结果这条链路比预想的长,也踩了不少坑。记录下来,也许对想做类似事情的人有用。

一、先想清楚给谁看

同一份材料,写给三个人看,得写三遍:

  • 给技术决策者:关心架构、部署成本、和同类产品的差异
  • 给开发者:关心怎么接、坑在哪
  • 给完全不懂的人:关心"这东西解决了我什么麻烦"

我最后做了两个产物:一份 17 KB 的研究文档(给前两类人),一条 4 分 10 秒的视频(给第三类人)。

这个拆分很关键。如果只做一份,通常的结局是文档太浅、视频太深,两边都不满意。

二、资料:一手优先,镜像兜底

我给自己定的原则是:所有关键数字必须有出处,且出处不能是二手转述。

所以信息来源只有三类:

  1. 官网首页与产品页
  2. 官方文档(带版本号的那套)
  3. 仓库 README 与 release notes

有个细节值得说:我这个环境的 github.com 连不上(返回 502),但 cdn.jsdelivr.net 和 GitHub Pages 是通的。所以 README 是从 jsDelivr 的镜像路径取的,内容与仓库一致。

Star 数这类会变的指标,用 img.shields.io 的 JSON 接口读,比截图靠谱:

curl -s https://img.shields.io/github/stars/infiniflow/ragflow.json
# {"message":"93k", ...}

另一个坑:官方文档存在 /docs/dev/ 和 /docs/v1.0.0-rc2/ 两套路径,前者会返回一个 283 字节的重定向壳页给你,看起来像"页面正常但没内容"。这类"HTTP 200 的假成功"最费时间——判断依据应该是内容长度,不是状态码。

三、把术语翻译成人话

技术内容讲给外行,难点不在简化,而在找到正确的类比。

我最后用了三个:

闭卷考试 → 开卷考试。 大模型像一个只背到训练那天的学生,公司内部资料它一份没见过。RAG 就是允许它答题前先翻书。

翻书比写字难。 这句话是整个视频的转折点。大多数人以为难点在"生成答案",其实在"找对页"。翻错了页,模型越聪明,错得越像真的。

切块的两难。 切得小,检索准但上下文断;切得大,上下文全但意思被稀释。没有哪个固定字数是对的——这个矛盾用天平式的左右对比一画就懂,用文字解释要三句话。

写口播稿时我给自己加了两条硬约束:

  • 每句不超过 40 字,中间必须有换气点
  • 不出现没有解释的英文缩写(RAG 除外,因为它必须出现)

第二条是我后来才意识到的重要:第一版稿子写得很"顺",但那是写给人看的顺,不是读给人听的顺。这两者差别很大。

四、出片:HTML 排版 → 截图 → ffmpeg

没有用剪辑软件,走的是纯代码路径:

HTML 版面(1920×1080)
   ↓ Chrome headless --screenshot
PNG
   ↓ ffmpeg(运动 + 字幕叠层)
无声视频
   ↓ 合并配音
成片

选这条路的理由很实在:版式可以复用。16 屏共用一套 CSS,改配色只改一个变量,比在 After Effects 里逐帧调快得多。

坑一:crop 滤镜里的 t 是假的

我一开始想让每屏有轻微的缓慢推镜(Ken Burns 效果),写的是:

crop=w='floor(2688*(1-0.05*t/16)/2)*2':h='floor(...)':x='(iw-ow)/2':y='(ih-oh)/2'

不报错,正常出片,看起来一切正常。

但 crop 的 width/height 只在初始化时求值一次,表达式里带 t 不会报错,只会静默失效。也就是说整条视频实际上是静态幻灯,一帧都没动。

我是怎么发现的?对比同一场景内相隔 10 秒的两帧,算 SSIM:

SSIM All: 0.998

0.998 意味着几乎完全一致。如果真有 3% 的缩放,文字边缘会明显位移,不可能到 0.998。

改用 zoompan 才是对的,它是少数几个逐帧求值的滤镜:

zoompan=z='1+0.045*on/360':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d=1:s=1920x1080:fps=30

改完再测,同场景 SSIM 降到 0.88——运动真实存在了。

这个教训挺通用的:默认滤镜是逐帧求值的,但 crop 的尺寸参数是例外。遇到"改了参数没反应还不报错",先怀疑求值时机。

坑二:这台机器的 ffmpeg 没有 drawtext

想加字幕,第一反应是 subtitles 或 drawtext 滤镜。查了一下:

ffmpeg -hide_banner -filters
# 没有 drawtext,没有 subtitles,没有 libass

这个构建没编译文本渲染。

解决办法是把字幕也当成图层:单独渲染一张 1920×1080 的透明 PNG(只有底部一行字),再用 overlay 叠上去。

Chrome 出透明图需要两个条件:

--default-background-color=00000000
# 且 HTML 里 body 必须 background:transparent

好处是字幕的字体、描边、渐变蒙版全部可控,比 libass 的样式能力强;坏处是每改一个字都要重新渲染。

坑三:时长要跟着配音走,不是反过来

第一版我先定死每屏 10 秒,再往里塞配音,结果有的屏话没说完就切了,有的屏说完了干等 4 秒。

正确顺序是:先生成配音,量出时长,再决定画面多长。

我的参数是每屏前留 0.55 秒、后留 0.75 秒,整轨用 loudnorm 归一到 -16 LUFS。这个留白量是试出来的,少了会显得赶,多了会显得拖。

五、配音:从系统 TTS 到云端 TTS

这部分是我被打回重做最多的地方。

第一版:macOS 自带的 say

say -v Tingting -r 172 -o out.aiff "文本"

优点是零成本、离线、立刻可用。缺点是听起来就是"机器在念字"——断句硬,句尾下沉,完全没有语气。

我一开始觉得"能听就行",但把成片发给朋友看,第一反应是"声音好怪"。

更好的选择其实一直连得上

排查下来发现,我环境里的火山引擎密钥其实覆盖了语音合成(豆包语音合成 2.0,seed-tts-2.0),只是接口路径不在我以为的地方:

POST https://openspeech.bytedance.com/api/v3/plan/tts/unidirectional
X-Api-Key: <key>
X-Api-Resource-Id: seed-tts-2.0

坑四:返回的是 NDJSON 流,不是 JSON

第一次拿到响应,看着像 JSON,但 json.loads 直接报错:

json.decoder.JSONDecodeError: Extra data

因为它是多个 JSON 对象首尾相接的流:

{"code":0,"data":"SUQzBAAA..."}
{"code":0,"data":"...AAA//OEx..."}
...
{"code":20000000,"data":null}

音频是 base64,被切成 20 多段分散在各个 chunk 里,最后以 code: 20000000 结束。正确做法是逐个 raw_decode、累加 data、再统一解码:

dec = json.JSONDecoder()
i, chunks = 0, []
while i < len(text):
    obj, i = dec.raw_decode(text, i)
    if obj.get("code") == 0 and obj.get("data"):
        chunks.append(obj["data"])
    if obj.get("code") == 20000000:
        break
audio = base64.b64decode("".join(chunks))

这个格式坑很典型:看着像 JSON 的,不一定是 JSON。单对象的 json.load 会成功解析第一个对象然后因为"多余数据"报错,这个报错信息很容易被误读成编码问题。

同一句话,五个音色的实测数据

45 个字的一句话(不含标点),测了时长和停顿:

音色时长字/秒停顿占比
系统 Tingting10.72s4.2012.7%
vv(通用女声)8.86s5.087.5%
高冷御姐9.46s4.7627.4%
爽快思思8.04s5.605.3%
甜美小源8.64s5.2118.6%
阳光青年(男)7.22s6.2313.4%

两个结论:

一、云端音色普遍比系统音色快 20–48%。 快听起来利落,但讲解视频里会显得赶。

二、停顿占比比语速更能决定"像不像人"。 高冷御姐字/秒只有 4.76,但停顿占 27.4%——说得稳、停得住,是纪录片旁白的节奏。爽快思思正好相反(5.6 字/秒、5.3% 停顿),那是短视频口播,不是讲解。

如果你的目标是"讲清楚一件事",看停顿占比,别只看语速。

顺带一个坑:音色和资源包是绑定的。我试"北京小爷"那个音色直接报:

resource ID is mismatched with speaker related resource

不是所有音色都能用在同一套资源下。

六、我学到的四件事

1. 素材的边界往往不是技术边界,是账号边界。 我一度以为"没有更好的 TTS",实际上接口一直是通的,卡在账号的一个开关上。遇到能力缺失,先确认是"做不到"还是"没开通"。

2. 静默失效比报错危险得多。 crop 带 t 不报错、/docs/dev/ 返回 200 但是空壳——这类问题不看结果只看状态码,永远发现不了。判断标准要落在内容上(SSIM、内容长度、音频时长),不是返回码。

3. 稿子的节奏比音色重要。 我把第一版稿子每句写 40–80 字,没有换气点,任何引擎念出来都赶。后来拆成短句、显式加停顿,即使还用系统语音,听感也提升明显。

4. 流水线要能重跑。 全程用脚本而不是手工操作,所以换配音这件事只需要重新生成音频、重跑一次合成,画面自动按新时长对齐。手工剪的话,这一改就是半天。

附:几个能直接抄的命令

远程抽视频片段(不下载整个文件):

ffmpeg -y -ss 3 -t 6 -i "https://example.com/demo.mp4" \
  -vf scale=640:-2 -an -c:v libx264 -crf 30 preview.mp4

前提是对方支持 HTTP Range。1.46 GB 的素材库,用这招抽 72 条预览只花了 6.7 MB。

判断两帧是否有真实运动:

ffmpeg -i a.png -i b.png -filter_complex ssim -f null /dev/null

同一个镜头内,SSIM 应该明显低于 1;如果接近 0.999,说明你的"动画"其实没动。

检查音频电平:

ffmpeg -i out.mp4 -af volumedetect -f null /dev/null

目标 mean_volume 在 -16 dB 附近,max_volume 不超过 -1 dB。


整个过程大概花了半天空闲时间,其中真正"创作"的部分(写稿、设计版式)不到三分之一,剩下都在解决工具链的脾气。这大概是所有自动化内容生产工作的常态。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

把一份技术研究,做成一集 4 分钟的讲解视频
/archives/ragflow-research-to-explainer-video
作者
Administrator
发布于
2026-11-02
许可协议
CC BY-NC-SA 4.0

评论