最近一直在折腾怎么把公众号里的长文章变成短视频试了好几种路子都不太满意。要么是文案提取出来之后排版乱得没法看要么是生成的视频就是图文PPT式翻页一眼假根本谈不上“生动”。后来我换了个思路干脆把这件事做成一个可复用的 Skill让 AI 走一套固定流程从公众号正文里拆出分镜再按分镜逐手绘生成动画最后合成配音和字幕输出成片。跑通之后效果挺惊喜现在把这个项目的完整拆解和实操过程整理出来给有同样需求的朋友当参考。这套方案的核心链路是公众号文章链接 → 正文提取与清洗 → LLM 拆解分镜脚本 → 分镜逐帧生成手绘 SVG 路径动画 → 渲染脚本录屏 → TTS 配音与字幕合成 → FFmpeg 合并成片。里面所有环节都能自动化一个人就能完成原本需要文案、分镜、插画、剪辑四个人协作的工作量。尤其适合做知识类口播号、产品说明视频、教程视频的人或者运营同学想给公众号文章做同步视频版本时用。1. 项目整体设计与思路拆解1.1 为什么选中“公众号文章 手绘动画”这个组合先说选题。公众号文章有一个其他内容源比不了的优势它天然就是结构化长文本。相比短视频平台的碎片文案、社交媒体的短贴公众号文章通常有清晰的分段、标题层级甚至配图这意味着它非常适合作为“视频脚本”的原料。我最初也考虑过直接把文章喂给 TTS 生成配音剩下画面用纯文字卡片应付但那种视频完播率很差说白了就是没有人愿意盯着一屏字看完三分钟。手绘动画视频的优势在于它能让抽象的文案转化成“正在被一笔一笔画出来”的视觉过程观众的注意力会被持续吸引住而且制作时不需要真实插画素材一套 SVG 路径绘制方案就能解决问题成本和版权风险都低。所以我最终确定了“公众号文章 → 手绘动画”的这样一个转化组合。目标是让 AI 一次接手从链接输入到短视频输出全链路自动完成中间不需要人手动截图、手动画图、手动剪配音。1.2 方案选型为什么用 Skill 而不是写死脚本或接入现成 SaaS市面上其实也有白板动画生成工具上传脚本就能出片但这类工具的致命问题是个性化程度低。你没法控制分镜内容的具体呈现没法改变画风更没法把文章里的数据、逻辑关系、引用的具体表述精确还原到画面里。对我这种有定制需求的人来说这些工具基本上只能用来尝鲜。写死一个 Python 脚本也不行。公众号文章的差异很大有纯文字、有图文混排、有一图流、有代码段、有表格。如果脚本把所有分支都写死维护成本会高到离谱。这时候“Skill”这种形态就很合适了——它不是一段固定代码而是一个包含指令约定、工具定义、流程约束的“技能包”。AI 拿到 Skill 后会按照里面定义的步骤执行每一步又能结合当前文章内容做针对性决策。说白了Skill 是“有套路且带脑子的执行者”写死的脚本是“没脑子的流水线”。打个比方写死脚本像是给工人一张富士康式的作业指导书规定必须按步骤按顺序操作而 Skill 更像是给一个聪明实习生一份项目执行手册手册里告诉他结果标准是什么、有哪些工具可以用、风险点在哪里但具体如何拆解任务他可以根据材料灵活发挥。这也是我最后选定 Skill 方案的核心原因公众号文章的不可控性决定了不能追求傻瓜式自动化要把“理解语义、拆解内容、设计表达”的空间留给大模型。1.3 Skill 的目录结构与运行方式我在设计这个 Skill 时按 Codex Skill 的通用规范组织目录实际上它也可以被 Claude Agent 或其他支持 Skill 机制的框架复用。目录结构大致如下wechat-to-sketch-video/ ├── SKILL.md ├── scripts/ │ ├── extract_article.py │ ├── build_scenes.py │ ├── render_scene.py │ └── make_video.sh ├── templates/ │ ├── scene_template.svg.j2 │ └── subtitle_template.srt.j2 └── assets/ ├── fonts/ └── bgm/SKILL.md是这个技能的核心说明文件里面写清楚执行流程、每一步调用的工具、输出的格式约定以及遇到异常情况时的处理规则。AI 在启动这个 Skill 后会先读取它然后会按照里面写的 workflow 一步步执行。scripts/下是具体干活儿的脚本templates/是生成 SVG 和字幕用的模板。我个人习惯把“决策”留给 LLM把“确定性操作”留给脚本这样整体稳定性会好很多。2. 核心细节解析与实操要点2.1 公众号正文提取从链接到干净文本要处理公众号文章第一步自然是拿到干净的正文。做到链路后面你会发现这一步直接决定视频质量的上限如果正文是残缺的或者夹杂大量导航推荐内容的后面分镜再准也会显得很怪。我早期试过直接用 Requests BeautifulSoup 去抓页面但公众号文章经过微信的 js 动态加载和多种跳转校验之后直接请求拿到的 HTML 经常不是正文内容。后来我调整成两个方案并行优先用微信公众号的“内容接口”读取适合自己公众号后台能拿到文章的场景其次用浏览器无头渲染抓取适合处理公开链接通过模拟真实浏览器环境拿到渲染后的完整 HTML。拿到 HTML 之后正文清洗是个容易被低估的难点。公众号正文的 DOM 结构中有很大一块是“推荐阅读”“相关文章”“名片”等干扰元素需要在清洗阶段剔除干净。我在extract_article.py里维护了一个黑名单配置专门匹配这些干扰区块的常见 CSS 类名、ID 和区块标题关键词。同时正文中的图片也要单独处理把图片地址和 alt 信息抽出来因为后续可能要用图片内容作为分镜参考。这段有一个非常实用的经验微信公众号的图片在直接下载时经常遇到“防盗链”问题带 referer 才能正常拉取。处理时要在下载函数里把Referer: https://weixin.qq.com/带上去否则会出现大量图片 403。清洗后的正文我会统一转成 Markdown 格式输出保留标题层级、段落、列表和表格结构。这样既方便后续 LLM 阅读也方便脚本做结构化切分。清洗代码里最核心的一段长这样def clean_wechat_html(html: str) - str: soup BeautifulSoup(html, html.parser) # 删除公众号正文中常见的干扰区块 for tag in soup.find_all(class_re.compile(recommend|related|qr-code|bottom|copyright)): tag.decompose() # 支持保留的图片节点单独抽出来 for img in soup.find_all(img): alt img.get(data-original) or img.get(src) img[src] alt if alt else img[src] # 转换正文为 markdown md html2text.HTML2Text() md.ignore_images False return md.handle(str(soup))2.2 文案转分镜大模型如何切出适合手绘表达的镜头分镜是整个流程中最依赖“AI 判断力”的一环也是这个 Skill 区别于普通脚本的关键。拿到 Markdown 格式的正文后Skill 会调用大模型接口让模型按给定规则拆解分镜。规则不能太抽象必须有可校验的输出格式我用的 JSON 结构大概是[ { scene_id: 1, quote: 原文中的关键句子, visual: 用于指导SVG绘制的画面描述如一个站在山顶的人形旁边有三条上升的箭头线, speech: 配音文本改写成口播风格通常30~50字, duration: 6.5 } ]在这一步我会专门在 Skill 的提示词里强调几条约束每句配音控制在 30 到 50 字之间因为手绘动画不是高信息密度视频配音太长会导致画面停留过久每个分镜只表达一个核心观点避免一个画面里塞进多个数据点视觉描述要尽量具体比如“三条上升的箭头”“一个圆形放大镜压在文本上方”“两个人物肩并肩对比”让后续渲染脚本能准确翻译成图形元素。这里有个特别有意思的细节如果不加约束大模型经常会把视觉描述写成“展示公司增长趋势”这种很虚的话。一旦让模型在 JSON 的visual字段里用严格的、可执行的语言描述画面要素手绘渲染的成功率会大幅提高。这个“从抽象表述强制降维成具体元素清单”的过程是整个环节里最值得调试的部分。2.3 手绘动画的实现原理与渲染方案手绘动画的“手绘感”其实并不玄幻核心就是一条路径由短到长、由细到粗地“画”出来。最经典的技术方案是基于 SVG 的stroke-dasharray和stroke-dashoffset动画。我们先准备好一条完整路径例如一个圆、一条曲线、一个文字的局部笔画然后利用路径长度的偏移量让线条看起来像是被一笔一笔绘制的。具体计算是这样做的每条路径的总长度通过getTotalLength()获取在动画开始的时刻stroke-dasharray设为路径总长stroke-dashoffset也设为路径总长这样线段被完全隐藏随着时间推移把stroke-dashoffset逐渐减小到 0线段就会被一点点“露出来”看起来就是手绘描边的效果。path.style.strokeDasharray ${length}px; path.style.strokeDashoffset ${length}px; path.animate( [ { strokeDashoffset: length }, { strokeDashoffset: 0 } ], { duration: drawingDuration, easing: ease-in-out, fill: forwards } );但真实的手绘动画不能每条线都用均匀速度画那会显得机械。我的做法是引入速度曲线变化主线起始慢、中间快、收尾又慢辅线则用“快速勾一下”的方式完成。再加上轻微的旋转、抖动和线条宽度变化就能营造出类似人手握笔的质感。如果需要更进一步的手绘感可以用 Rough.js 库生成带手绘质感的路径——它的线条不是绝对平滑的而是带微小的随机抖动一眼看去就很“手绘”。我在项目里默认用 Rough.js 生成草稿式构图再叠加笔刷描边动画效果相当能打。渲染环节我选了 Node.js Puppeteer 方案。Puppeteer 打开一个本地 HTML 页面页面内嵌 SVG 场景每个分镜对应一帧动画。Puppeteer 控制页面播放动画同时用其内置的page.screencast()或者采集帧的方式把动画过程录制成视频帧序列或 webm 片段。这种方法的好处是渲染效果所见即所得CSS 动画和 SVG 交互都能完整保留比用 OpenCV 凑图或 PIL 拼帧要灵活太多。2.4 配音、字幕与背景音乐合成分镜中的speech字段会交给 TTS 服务生成配音。我测试过好几款 TTS中文口播场景下优先选带情感预测和标点停顿能力的引擎最后合成出来不会有很重的机械感。每个分镜的配音单独生成音频文件不仅方便后续逐句校对也能按配音实际时长回推调整画面停留时间做到音画同步。字幕处理上我没有直接用配音引擎返回的边字幕而是根据每句配音的时间轴自己切分生成标准 SRT 文件。每句字幕对应一个分镜时间轴和分镜的配音 MP3 对齐这样即使某一句 TTS 时长超出预期也可以在下游合并时整体偏移处理。背景音乐我从本地素材库选了几段纯音乐循环通过 FFmpeg 混流时控制音量背景音乐音量压到配音音量的 15% 左右并加 fade in/fade out避免抢戏。这里要特别注意响度平衡如果背景音乐音量大于 -30 LUFS配音就很难听清最终成片会被观众直接划走。3. 实操过程与核心环节实现3.1 编写 SKILL.md把流程固化成 AI 可执行的指令这是整套方案里最核心的文件我直接把我实际使用的SKILL.md的关键内容展示出来方便大家直接改来用。注意这个文件不是给人看的说明书而是给 AI 读的执行手册所以描述要准确、可操作、可校验避免模糊的表达。# WeChat Article to Sketch Video Skill ## 目标 将一个微信公众号文章的链接转换为一段带配音和字幕的手绘动画短视频。 ## 输入 - article_url: 微信文章链接 - output_dir: 输出目录 ## 执行流程 1. 调用 scripts/extract_article.py 提取并清洗文章正文 保存为 markdown 格式到 output_dir/article.md。 如果请求失败尝试无头浏览器方案。 2. 读取 article.md调用大模型解析分镜 输出 output_dir/scenes.json。 scenes.json 为数组每个元素包含 scene_id、quote、visual、speech、duration 字段。 visual 字段必须是可以直接转换为 SVG 元素的具体描述 禁止出现抽象形容词。 3. 根据 scenes.json 逐场景生成 SVG 文件 使用 templates/scene_template.svg.j2 渲染 保存到 output_dir/scenes/scene_xx.svg。 4. 使用 scripts/render_scene.py 启动 Puppeteer 逐个场景渲染手绘动画录屏输出 video_part_xx.webm。 5. 按 scenes.json 中的 speech 调用 TTS 生成 audio_part_xx.mp3再汇总生成 subtitles.srt。 6. 使用 scripts/make_video.sh 将每个场景的 视频片段、配音、字幕合并成最终 output_dir/final.mp4。 ## 注意事项 - 如果正文提取结果包含“推荐阅读”“相关文章”等干扰内容必须重新清洗。 - 分镜时长不要超过 10 秒如果配音超出拆成两个分镜。 - 手绘线条动画必须遵循“先轮廓后细节”的绘制顺序。为什么要写得这么细因为如果SKILL.md里只说“提取文章然后生成视频”AI 很可能在某个环节偷懒比如直接生成一个静态 SVG 文件而不是逐帧动画。把流程、输入输出、校验标准、异常处理写明之后AI 才能真正端到端地跑完整个 Skill。3.2 从文章到分镜调用大模型时的提示词设计实际拆解分镜时提示词的质量决定了 80% 的成片效果。我用的提示词大概长这样放在build_scenes.py里作为系统消息你是一个资深视频编导请把下面的公众号文章改写成适合手绘动画视频的分镜脚本。要求 1. 每个分镜只表达一个核心信息不要贪多。 2. 配音文本必须口语化控制在30到50字便于TTS朗读。 3. visual字段必须只包含具象名词和动作描述方便后续转成SVG绘图。 例如一个圆桌上放着三本书右边画一个向上的曲线箭头。 4. 如果原文有数据尽量用图形元素呈现而不是只用文字念出来。 5. 返回 JSON 数组不要输出任何其他文字。 文章内容 在此插入清洗后正文这样的提示词设计在实操中有一个隐藏的好处因为要求返回严格 JSON 数组即使某次生成的visual有点跑偏后续的渲染脚本也能通过 JSON 校验发现问题并报错而不是把错的内容静默吞掉有助于快速定位是哪一步出了问题。3.3 手绘 SVG 场景生成的模板与动态绘制效果分镜确定之后接下来就是把 JSON 转成 SVG。这个环节不能靠大模型直接输出完整 SVG因为模型生成的 SVG 经常带有不存在的字体引用、滤镜兼容性问题而且动画参数不可控。我采用的是“模板 数据填充”的方式场景模板定义好常用的基础元素比如背景、标题栏、装饰元素然后把visual字段传给大模型让模型只负责输出这个场景里出现的具体图形元素清单和动画顺序再将元素渲染到模板的指定分组里。举个实际例子分镜的visual如果是“三个上升的箭头指向一个圆形的目标靶”渲染脚本就会在场景中创建三条不同角度的 SVG 路径同时给三条路径设置不同的stroke-dashoffset动画起点和播放延迟形成依次绘制的节奏。为了让手绘感更强我还会对每条路径做分段处理先画箭头杆停 0.2 秒再画箭头头部。这种细腻的动画节奏感是手绘视频“生动形象”的关键来源。基础元素模板里我预设了以下几种可复用组件标题文字组件带有打字机效果或逐字显现效果数据曲线组件一条折线从左到右生长末尾带数据标签人物对话组件两个简单圆形人物头像加对话框配合轻微摆动步骤流程图组件三个矩形框依次被画出并用箭头串联。这几种组件基本能覆盖大部分知识类公众号文章的可视化需求。遇到超出预设的场景渲染脚本会把底层 SVG 元素作为基础层暴露给模型自行组合不至于限制发挥。3.4 录屏、配音到最终成片一条命令跑完全流程到这一步所有零件已经备齐各分镜的动画视频片段、分镜配音、字幕文件和背景音乐。最后用一条 Shell 脚本把这些内容按顺序合并起来脚本中 FFmpeg 的处理逻辑是这样的#!/bin/bash # make_video.sh - 合并视频、音频、字幕 SCENES_DIR$1 OUTPUT$2 BGM$3 # 1. 生成 concat 列表文件 for f in $SCENES_DIR/video_part_*.webm; do echo file $f concat.txt done # 2. 视频流合并 ffmpeg -y -f concat -safe 0 -i concat.txt -c copy video_merged.webm # 3. 合并所有配音 for f in $SCENES_DIR/audio_part_*.mp3; do echo file $f audio_concat.txt done ffmpeg -y -f concat -safe 0 -i audio_concat.txt -c copy audio_merged.mp3 # 4. 混合配音与背景音乐、嵌入字幕 ffmpeg -y -i video_merged.webm -i audio_merged.mp3 -i $BGM \ -filter_complex \ [1:a]volume1.0[voice];[2:a]volume0.15[bgm];[voice][bgm]amixinputs2:durationfirst[aout] \ -c:v libx264 -c:a aac -vf subtitlessubtitles.srt $OUTPUT这里踩过一个不小的坑如果直接按 concat 顺序合并视频片段但某个分镜的配音时长明显大于动画时长视频画面就会在结尾空转。所以我在脚本里加入了一步修正逻辑先扫描每个 MP3 的时长如果大于 SVG 动画设定时长就自动把对应片段做一个减速处理。这个细节比较重要实测下来能避免至少三成“音画不同步”的问题。整套流程跑通后我只需要执行一行命令输入文章链接和输出目录等几分钟就能拿到一个完整的手绘风格口播视频。中间完全不需要人打开剪辑软件。4. 常见问题与排查技巧实录4.1 公众号文章源数据相关的坑这一环节是实际操作中最容易出问题的典型的表现是链接明明能打开但脚本提取出来却是空内容或者残缺内容。正文区抓不全多半是遇到了页面懒加载只抓到了首屏 HTML。处理办法是换成无头浏览器渲染完成后再抓取或者主动滚动几次页面触发懒加载。我在 Puppeteer 里会先执行window.scrollTo(0, document.body.scrollHeight)再延时两到三秒抓取命中率明显提高。图片 403公众号图片有防盗链必须带Referer: https://weixin.qq.com/否则只能拿到裂图占位符。如果下载下来之后还想做后期美化建议直接用原图地址不要用微信的压缩缩略图。正文混入公众号名片或推荐内容这种干扰很难用统一的规则全量清除因为不同账号的页面结构不太一样。我的处理办法是在清洗阶段保留一个“正文非正文判定”的模型调用让大模型在拿到清洗后的正文时再次确认是否有无关内容有就标注删除。这一步能解决大多数漏网之鱼。4.2 手绘动画渲染期的常见问题SVG 路径动画看起来简单实际跑起来会发现很多反直觉的问题。路径长度获取为 0这个问题在隐藏元素上很容易出现。Puppeteer 打开页面时如果路径元素的display:none或者还没有进入视口getTotalLength()就会返回 0动画自然不显示。解决办法是在获取长度前强制把元素设为visibility: visible或者延迟到页面完全渲染后再计算。动画生硬不自然此前提到过如果所有线条都用同一速度画看起来就像机械扫描。我把路径按照视觉主次分组给每组设置不同的时长和延迟主路径优先绘制装饰性路径放到最后并且统一加上easeInOutCubic的缓动函数手绘感会大幅提升。某条路径画到一半卡住多半是因为该路径不是一条连续的 path而是由多个子路径拼接而成。stroke-dasharray动画对复合路径的处理比较麻烦我的经验是在生成 SVG 时尽量让每条元素都是一条完整闭合或开放的单独路径避免一个path里塞多个M指令。4.3 音画同步与成片质量问题音画不同步是这类自动化视频最容易劝退人的缺陷处理起来头绪不少。TTS 时长与动画时长不匹配最粗暴的方法是根据 TTS 返回的音频时长重新裁切视频片段把对应片段的播放速度设置为实际配音时长 / 动画设计时长。我实测过轻微的速度变化0.8 到 1.2 倍范围人眼几乎感知不到所以这个方法很实用。字幕超前或滞后SRT 字幕如果单独生成经常出现和语音对不上的问题。我后来改成基于音频能量检测来做 VAD 语音活动检测把每句语音的起止点检测出来再生成对应的字幕轨。这样虽然多跑一步但字幕能精确卡在语音上。画面模糊如果用 Puppeteer 的录屏接口跑默认参数输出视频码率可能不够画面里细线会糊成一片。拍摄视频截图时把viewport设为全高清 1920x1080像素比设为 2最终成片用libx264编码并指定-crf 18画质就比较稳定了。4.4 批量生产时的稳定性建议当这个 Skill 真正用于日常内容生产还需要考虑批量跑量时的稳定性问题。我的建议是给每个任务建一个独立工作目录中间产物全部保存下来这样任何一个分镜渲染失败只需要重新处理那一个分镜而不是整个视频推倒重来。另外给每一步加上超时限制例如 Puppeteer 单场景渲染超过 30 秒就自动终止并重新尝试避免某个坏分镜把整条流水线堵死。生产环境实测下来一次任务的失败率控制在 10% 以内是可以做到的。这个容错率已经足够支撑日常更新。如果是要做到完全无人值守建议再接一个 OpenAI 的 function calling 或独立代理机制失败后自动调用一次大模型修正 SVG再进行下一轮渲染。5. 实操心得与后续扩展方向我自己实际操作一段时间后最大的体会是这个 Skill 的“上限”其实取决于你给 LLM 多大的创作空间而不是取决于渲染脚本有多复杂。早期我把每个场景的 SVG 动画方式都写得很死结果视频虽然稳定但每个分镜看起来都像同一个模板套出来的观众很快就审美疲劳。后来我改成只约束“分镜的输出 JSON 结构”和“动画绘制的顺序规范”把画面元素构成、布局位置、图形组合的决策权完全交给大模型反而生成了很多我自己想不到的好画面。另外手绘动画视频这个领域真正拉开差距的其实不是技术而是“踩中表达节奏”。同样的文章有人切出来的分镜是平铺直叙的流水账有人切出来的分镜是有起承转合的微型故事。我建议拿到一篇公众号文章后先让大模型用一段话概括核心观点再围绕核心观点反推分镜而不是按原文段落顺序机械切分。这个“先有主题后拆镜头”的小技巧对提升成片质量的帮助比其他任何调参都明显。最后聊一下后续可以扩展的方向。目前这套方案只支持输出全屏手绘动画一种风格但底层架构上场景模板是完全可替换的。你完全可以做一套“卡片翻页风格”的模板或者“PPT 简约风格”的模板同一个 Skill 里通过输入参数切换画风。还有一个我在考虑的方向是接入公众号后台的定时发布能力让“文章发布后自动生成手绘短视频并回传到视频号”成为一条龙的自动化流水线。如果你本身有做知识付费或课程内容也可以把这套 Skill 接到文章合集上让文章体系批量变成视频课程一个人运营一个视频矩阵就不再是空想了。