1. 从图文到短视频的自动化链路拆解
公众号文章和视频号短视频之间,隔着的不是简单的“复制粘贴”,而是一整套内容形态的翻译工作。我做了三年多的内容自动化工具链,踩过最大的坑就是一开始想用“一键搬运”的思路解决问题——把文章里的图片抽出来,配上TTS语音,再随便叠个背景音乐就发出去。结果视频号那边完播率惨不忍睹,评论区全是“这什么玩意儿”。后来我才想明白,图文和视频是两种完全不同的消费场景:读者看文章可以跳读、回看、暂停思考,但短视频观众是在信息流里被动接收,前3秒抓不住注意力就直接划走。
所以这套智能体基建的核心目标,不是“把文章变成视频”,而是“把文章重新创作为适合视频号消费的短视频”。这个定位差异决定了后面所有技术选型和流程设计的走向。我最终跑通的方案是:用智能体做内容理解和脚本重构,用Whisper做语音转写和字幕对齐,用FFmpeg做视频合成与渲染,用CDP做浏览器端的自动化操作。整套链路跑下来,一篇2000字左右的公众号文章,从输入到输出一条60秒左右的短视频,全自动耗时大约3到5分钟,人工只需要做最后的审核和发布确认。
这套东西适合谁?如果你是一个人运营公众号同时还想做视频号,每天被“写文章已经够累了还要剪视频”折磨得不行,那这套方案就是给你准备的。如果你是小团队的内容运营,想用最低的人力成本把图文内容二次分发给视频号渠道,也可以直接抄作业。但如果你追求的是电影级质感或者复杂的转场特效,那这套方案可能满足不了你——它的定位是“稳定、批量、可复现”,而不是“惊艳”。
1.1 为什么选择智能体而不是传统脚本
一开始我试过用纯Python脚本做这件事,写了一大堆正则表达式去提取文章段落、判断哪些句子适合做口播、哪些图片适合做画面。结果维护成本极高——公众号文章的排版风格稍微一变,正则就全废了。后来换成智能体方案,核心变化在于:智能体不是靠规则匹配,而是靠语义理解来做决策。
举个例子,一篇文章里可能有五个小标题,传统脚本会按顺序全部提取,但智能体会判断哪个小标题下的内容最适合做短视频——通常是那个有具体案例、有数据、有冲突感的段落。再比如,文章里可能有大段的理论阐述,智能体会自动把它压缩成一句口语化的总结,而不是原封不动地念出来。这种“理解后重构”的能力,是规则脚本给不了的。
我目前用的是基于大模型API构建的智能体框架,核心工作流分三步:第一步是内容理解,把公众号文章的HTML结构解析成纯文本后,让智能体判断文章的核心观点、情绪基调、目标受众;第二步是脚本生成,根据视频号的消费特点,把文章重构为“钩子开头+核心信息+行动号召”的三段式脚本;第三步是素材规划,决定每个脚本段落对应什么画面——是用文章里的配图,还是用AI生成的图片,还是用纯色背景加字幕。
1.2 技术栈选型的底层逻辑
整套链路里,FFmpeg负责所有视频合成工作,包括图片序列转视频、音频混流、字幕烧录、转场效果。选FFmpeg的理由很简单:它是命令行工具,可以被智能体直接调用,不需要打开任何图形界面。而且FFmpeg的滤镜系统极其强大,用zoompan滤镜可以做图片的缓慢缩放效果,用xfade滤镜可以做转场,用drawtext可以做字幕渲染,基本上短视频需要的视觉效果它都能覆盖。
Whisper的角色是语音转写和字幕时间轴对齐。虽然TTS引擎本身可以输出每个字的时长信息,但实际测试下来,TTS输出的时间轴和最终音频文件经常有几十毫秒的偏移,直接用来做字幕会出现“字音不同步”的问题。用Whisper对生成的音频做一次强制对齐,可以把字幕时间轴的精度控制在50毫秒以内,肉眼基本看不出偏差。我用的是Whisper的small模型,在普通CPU上跑一段60秒的音频大约需要8到12秒,完全可以接受。
CDP(Chrome DevTools Protocol)负责浏览器端的自动化操作,主要是两个场景:一是自动登录公众号后台获取文章内容,二是自动上传视频到视频号后台。CDP的优势在于它直接控制Chrome浏览器,不需要模拟鼠标点击那种脆弱的方案,而且可以绕过一些前端加密逻辑——比如公众号后台的登录态维护,用CDP直接操作浏览器上下文比用HTTP请求模拟要稳定得多。
2. 核心环节的实操细节与避坑指南
2.1 文章内容提取:别直接用HTML
从公众号后台获取文章内容,最直接的方式是用CDP打开文章编辑页面,然后执行JavaScript提取#js_content元素的innerHTML。但这里有个坑:公众号文章的HTML里包含大量内联样式和>ffmpeg -loop 1 -i input.jpg -filter_complex \ "zoompan=z='min(zoom+0.001,1.5)':d=25*5:s=1080x1920:fps=25" \ -t 5 -c:v libx264 -pix_fmt yuv420p segment_01.mp4
这段命令的意思是:把一张静态图片做成5秒的视频,画面从原始大小缓慢放大到1.5倍,输出分辨率1080x1920(竖屏),帧率25fps。d=25*5表示持续5秒(25帧每秒乘以5秒),zoompan的z参数控制缩放速度,min(zoom+0.001,1.5)表示每帧增加0.001倍缩放,最大到1.5倍。
如果画面是纯色背景加字幕,用color源加drawtext滤镜:
ffmpeg -f lavfi -i color=c=0x1a1a2e:s=1080x1920:d=5:r=25 \ -vf "drawtext=text='你的第一句话':fontfile=/path/to/font.ttf:fontsize=64:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2" \ -c:v libx264 -pix_fmt yuv420p segment_02.mp4第二步,把所有片段拼接。如果片段之间的编码参数完全一致,可以用concat协议直接拼接,速度极快:
ffmpeg -f concat -safe 0 -i filelist.txt -c copy merged.mp4filelist.txt里每行写一个片段路径,格式是file 'segment_01.mp4'。这种拼接方式不重新编码,所以速度是秒级的。但前提是所有片段的编码参数必须完全一致,否则会出现音画不同步的问题。
第三步,烧录字幕和混音。字幕用subtitles滤镜加载SRT文件,背景音乐用amix滤镜和口播音频混合:
ffmpeg -i merged.mp4 -i bgm.mp3 -filter_complex \ "[0:v]subtitles=subtitle.srt:force_style='FontSize=18,PrimaryColour=&HFFFFFF&'[v]; \ [0:a]volume=1.0[a1];[1:a]volume=0.15[a2];[a1][a2]amix=inputs=2:duration=first[a]" \ -map "[v]" -map "[a]" -c:v libx264 -c:a aac output.mp4这里背景音乐的音量设为0.15,口播音量保持1.0,这样人声清晰可辨,背景音乐只是氛围点缀。字幕字号18在1080x1920的竖屏上大约占画面宽度的5%,阅读舒适度最好。
3. 智能体编排与CDP自动化的工程实践
3.1 智能体工作流的编排方式
整套链路的智能体编排,我用的是状态机模式而不是自由对话模式。原因很简单:自由对话模式下,智能体可能会“忘记”上一步的输出格式要求,或者在中途产生不必要的追问。状态机模式下,每个步骤的输入输出格式都是严格定义的,智能体只负责在当前步骤内做决策,不负责流程控制。
具体来说,整个流程分为五个状态:FETCH_ARTICLE(获取文章)、GENERATE_SCRIPT(生成脚本)、SYNTHESIZE_AUDIO(合成语音)、ALIGN_SUBTITLE(对齐字幕)、RENDER_VIDEO(渲染视频)。每个状态完成后,把输出写入一个JSON文件,下一个状态从文件读取输入。这样做的好处是:如果某个步骤失败了,可以从失败的状态重新开始,不需要从头跑一遍。
状态之间的转换条件也很简单:只要当前状态的输出文件存在且格式校验通过,就自动进入下一个状态。如果校验失败,就重试当前状态,最多重试3次。3次都失败就标记为人工介入,把中间产物保存下来供排查。
3.2 CDP自动化的稳定性技巧
CDP控制浏览器做自动化,最大的敌人是页面加载时机。公众号后台的编辑器是单页应用,页面加载完成后还会异步加载很多资源,如果加载完就立刻执行JavaScript,经常找不到目标元素。我的做法是用CDP的Page.loadEventFired事件作为触发点,然后加一个setTimeout等待2秒,再执行元素查找。
另一个坑是登录态维护。公众号后台的登录态存在Cookie里,有效期大约4到6小时。如果自动化任务跑的时间比较长,中途登录态过期了,后续操作就会全部失败。我的解决方案是在CDP启动时先检查登录态,如果发现已过期就暂停任务并发送通知,而不是让任务带着失效的登录态继续跑。
视频号上传的CDP自动化更复杂一些,因为上传页面有文件选择器。CDP本身不能直接操作文件选择器,但可以用DOM.setFileInputFiles方法给<input type="file">元素直接设置文件路径。具体操作是先用Runtime.evaluate找到文件输入框的DOM节点,然后用DOM.setFileInputFiles把本地视频文件路径传进去。这样就不需要模拟点击“选择文件”按钮再操作系统对话框了。
注意:视频号上传页面在文件设置后不会自动开始上传,还需要触发一次
change事件。可以用Runtime.evaluate执行document.querySelector('input[type=file]').dispatchEvent(new Event('change'))来触发。这个细节在官方文档里没有写,是我抓包分析前端代码才发现的。
3.3 批量处理的队列设计
如果每天要处理多篇文章,就需要一个队列系统来管理任务。我的做法是用一个简单的JSON文件做任务队列,每篇文章对应一个任务对象,包含文章URL、当前状态、重试次数、输出路径等字段。一个Python脚本循环读取队列,取出状态为PENDING的任务,依次执行五个状态,完成后把状态改为DONE。
队列的并发度我设为1,也就是串行处理。原因是FFmpeg渲染视频时CPU占用很高,如果同时跑多个任务,每个任务的速度都会变慢,而且容易出现内存不足的问题。串行处理虽然总耗时更长,但稳定性好得多。实测下来,一篇2000字的文章从获取到输出视频,串行处理大约需要3到5分钟,一晚上跑20篇完全没问题。
4. 常见问题排查与效果优化
4.1 字幕不同步的三种情况和修复方法
字幕不同步是这套链路里最高频的问题,我遇到过三种情况,每种的原因和修复方法都不一样。
第一种是整体偏移,所有字幕都比音频慢了或快了固定的时间。这通常是Whisper对齐时的起始时间计算错误导致的。修复方法是在SRT文件生成后,给所有时间戳统一加上或减去一个偏移量。我写了一个简单的脚本,用ffprobe获取音频的实际起始时间,然后和Whisper输出的第一个词的时间戳做对比,差值就是偏移量。
第二种是渐进偏移,字幕开头同步,越到后面偏差越大。这通常是TTS引擎的语速不稳定导致的——有些句子念得快,有些念得慢,但Whisper对齐时用的是均匀时间分配。修复方法是改用词级时间戳而不是句级时间戳,让每个词的时间都精确对齐。Whisper的word_timestamps=True参数可以输出词级时间戳,虽然会增加一些计算时间,但字幕精度会大幅提升。
第三种是个别句子错位,大部分字幕同步,但某几句突然跳到了错误的位置。这通常是Whisper识别错误导致的——它把某个词听成了另一个词,导致后续所有词的时间戳都错位了。修复方法是在对齐算法里加入编辑距离校验,如果某个短句的匹配度低于阈值,就跳过这个词,用前后词的时间戳做插值。
4.2 视频渲染速度优化的四个手段
FFmpeg渲染是整套链路里最耗时的环节,一条60秒的1080x1920视频,用默认参数渲染大约需要40到60秒。我通过四个手段把渲染时间压缩到了15到20秒。
第一个手段是用硬件加速编码。如果你的机器有NVIDIA显卡,可以用h264_nvenc编码器替代libx264,渲染速度能提升3到5倍。命令里把-c:v libx264换成-c:v h264_nvenc就行。如果没有NVIDIA显卡,Intel核显可以用h264_qsv,AMD显卡可以用h264_amf。
第二个手段是降低中间片段的编码质量。单个片段在拼接前不需要很高的画质,可以用-crf 28(默认是23)来加快编码速度。最终输出时再用-crf 20保证画质。这样整体渲染时间能减少30%左右。
第三个手段是预生成纯色背景视频。如果多个片段都用同一种纯色背景,可以预先生成一个长视频,然后用trim滤镜截取不同时间段,避免重复渲染。这个优化在片段数量多的时候效果明显。
第四个手段是用-preset ultrafast。libx264的preset参数控制编码速度和压缩率的平衡,ultrafast比默认的medium快很多,代价是文件体积大一些。对于短视频号发布来说,文件体积大一点无所谓,反正平台会重新压缩。
4.3 内容质量的三个校验点
自动化生成的内容,发布前必须过三道校验,否则很容易翻车。
第一道是事实校验。智能体在重构脚本时可能会“脑补”一些文章里没有的数据或结论。我的做法是在提示词里明确要求“只使用输入文本中出现的信息,不要添加任何外部知识”,同时在输出后用一个简单的规则校验——如果脚本里出现了数字,就去原文里搜索这个数字,找不到就标记为可疑。
第二道是敏感词校验。虽然智能体本身有一定的安全过滤,但为了保险起见,我还是会在脚本生成后过一遍敏感词库。这个词库不需要很大,主要是行业相关的禁用词和平台明确限制的词汇。
第三道是时长校验。用ffprobe获取最终视频的时长,如果超过65秒就触发重新生成。视频号虽然支持更长的视频,但60秒左右的完播率最高,超过90秒的完播率会断崖式下降。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 修复方案 |
|---|---|---|---|
| 视频无声音 | 音频流未正确映射 | 用ffprobe检查输出文件是否有音频流 | 检查-map参数是否包含了音频流 |
| 字幕显示为方块 | 字体文件不支持中文 | 用fc-list查看系统字体 | 指定支持中文的字体文件路径 |
| 图片变形 | 未设置正确的宽高比 | 检查输入图片的分辨率 | 用scale滤镜加force_original_aspect_ratio=decrease |
| 渲染速度极慢 | 使用了软件编码 | 查看FFmpeg日志中的编码器名称 | 切换到硬件加速编码器 |
| CDP连接失败 | Chrome未以调试模式启动 | 检查Chrome启动参数 | 添加--remote-debugging-port=9222 |
| Whisper识别乱码 | 音频采样率不匹配 | 用ffprobe检查音频采样率 | 统一转换为16kHz单声道 |
5. 从单篇到批量的扩展思路
这套方案跑通单篇之后,下一步自然是扩展到批量处理。我目前的做法是维护一个“待处理文章列表”,每天定时从公众号后台拉取最新发布的文章,自动加入队列。队列处理完成后,生成一个审核页面,把所有生成的视频列出来,我只需要快速过一遍,确认没问题就批量上传到视频号。
批量处理带来的新问题是内容同质化。如果连续几篇文章都是同一个话题,生成的视频脚本可能会很相似。我的解决方案是在智能体生成脚本时加入“多样性约束”——如果检测到最近3条视频的脚本开头句式相同,就强制要求智能体换一种开头方式。这个约束通过维护一个“最近开头句式”的列表来实现,每次生成新脚本时把列表传给智能体作为参考。
另一个扩展方向是多平台适配。同样的文章内容,除了生成视频号短视频,还可以生成抖音版本、小红书版本。不同平台的视频规格不同:视频号是1080x1920竖屏,抖音也是竖屏但推荐分辨率是1080x1920,小红书支持竖屏和方形。我的做法是把视频规格参数化,在渲染时根据目标平台选择不同的分辨率和时长策略。抖音的完播率对时长更敏感,所以抖音版本的脚本会压缩到45秒左右;视频号可以稍微长一点,60秒左右比较合适。
最后再分享一个我在实际使用中总结的小技巧:背景音乐的选择比想象中重要。一开始我用的是固定的几首背景音乐,结果发现同样的脚本配不同的音乐,完播率能差20%以上。后来我建了一个背景音乐库,按情绪分类(轻松、紧张、励志、温情),让智能体根据文章的情绪基调自动选择匹配的音乐。这个改动虽然小,但对最终效果的影响非常明显。