☰
智能体驱动公众号文章转视频:ffmpeg与whisper自动化实践
2026/10/29 8:32:43 网站建设 项目流程

1. 从图文到视频的自动化链路拆解

公众号文章和视频号短视频之间,隔着的不是简单的“复制粘贴”,而是一整套内容形态的翻译工作。我做了三年多的内容自动化工具链,踩过最大的坑就是一开始想用剪映的模板批量套——结果发现每篇文章的段落结构、重点分布、配图位置都不一样,模板根本套不进去。后来转向用智能体来驱动整条流水线,才算真正跑通。

这套方案的核心思路是:把公众号文章当作结构化数据源,用智能体做内容理解和分镜决策,再调用 ffmpeg 和 whisper 完成音视频合成。整个链路可以拆成四个阶段——内容抓取与清洗、智能体分镜脚本生成、语音合成与字幕对齐、视频合成与导出。每个阶段都有独立的工具选型和参数调优空间,下面逐个展开。

先说说为什么选智能体而不是写死脚本。公众号文章的排版千变万化,有的用大量小标题,有的全是长段落,有的图文混排密集。如果用固定规则去切分,遇到不常见的排版就崩了。智能体的优势在于它能理解语义边界——比如它知道“首先、其次、最后”这种逻辑词应该对应不同的分镜,也知道一段话里哪句是核心观点、哪句是补充说明。这种语义级的判断,靠正则表达式是做不到的。

另一个关键决策是用 ffmpeg 而不是剪映或 PR 的 API。剪映虽然有开放接口,但批量处理时的稳定性和可控性远不如 ffmpeg。ffmpeg 是命令行工具,意味着你可以把它嵌入任何自动化流程,参数完全透明,出了问题也能精确定位。而且 ffmpeg 支持硬件加速,批量渲染几十条视频时,速度优势非常明显。

提示:ffmpeg 的 GPL 和 LGPL 版本在商用场景下有区别。GPL 版本包含 libx264 等 GPL 组件,如果分发给第三方需要注意合规;LGPL 版本功能少一些但限制更宽松。内部使用的话两者都可以,对外发布建议确认一下授权条款。

整个链路的输入是一篇公众号文章的 HTML 或 Markdown,输出是一条带字幕、带背景音乐、带转场的竖屏短视频。中间涉及的核心工具包括:用于内容抓取的 HTTP 客户端、用于语义理解的智能体框架、用于语音合成的 TTS 引擎、用于字幕时间戳对齐的 whisper、以及用于视频合成的 ffmpeg。下面按实操顺序逐一拆解。

2. 内容抓取与结构化清洗的实操细节

公众号文章的抓取不是简单的requests.get就能搞定。页面里的正文、配图、引用块、代码块混在一起,直接丢给智能体的话,它会浪费大量 token 在处理无关的导航栏和推荐位上。所以第一步必须做结构化清洗。

我常用的做法是先用 CDP(Chrome DevTools Protocol)拿到渲染后的 DOM,再用 Readability 类的算法提取正文区域。CDP 的好处是能拿到 JavaScript 动态加载后的内容,而且可以模拟真实浏览器的行为,避免被识别为爬虫。具体操作上,用 Playwright 或 Puppeteer 启动一个 headless 浏览器,监听Page.loadEventFired事件后,执行document.querySelector定位正文容器。

# 用 Playwright 抓取渲染后的正文 HTML from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() page.goto(article_url, wait_until="networkidle") # 等待正文容器出现 page.wait_for_selector("#js_content", timeout=10000) content_html = page.inner_html("#js_content") browser.close()

拿到 HTML 后,下一步是转成结构化数据。我一般会转成 JSON,包含title、sections(每个小节含heading和paragraphs)、images(含url和position)三个字段。这样智能体拿到的是一个干净的树形结构,而不是一堆混杂的标签。

清洗阶段有几个容易忽略的细节。第一,图片的 alt 文本要保留,智能体在生成分镜时可以参考 alt 来判断图片内容,决定是否需要在视频中展示。第二,代码块要单独标记,因为代码在视频里展示的方式和普通文字不同,通常需要等宽字体和更长的停留时间。第三,引用块要保留层级关系,视频里可以用不同的背景色或边框来区分。

注意:抓取频率要控制。同一域名下建议间隔 3 秒以上,避免触发风控。如果文章量大,可以维护一个抓取队列,用指数退避策略处理失败重试。

清洗完成后,我会做一个“内容密度检查”。具体做法是计算每个段落的字符数和信息熵,把过短的段落(比如少于 20 字)合并到相邻段落,把过长的段落(超过 500 字)按句号切分。这一步的目的是让后续的分镜粒度更均匀,避免出现“一句话一个镜头”或“五分钟一个镜头”的极端情况。

3. 智能体分镜脚本生成的核心逻辑

分镜脚本是整个链路里最考验智能体能力的环节。它需要完成三件事:决定哪些内容进视频、决定每段内容的展示形式、决定镜头之间的衔接方式。

我用的智能体框架是基于 ReAct 模式的,核心 prompt 里会明确几个约束条件。第一,视频总时长控制在 60 到 90 秒,对应大约 200 到 300 字的解说词。第二,每个分镜的解说词不超过 40 字,对应 8 到 12 秒的展示时间。第三,必须从原文中提取一个“钩子句”放在开头,用来抓住注意力。

智能体的输出是一个 JSON 数组,每个元素包含narration(解说词)、visual_type(展示类型,如text_card、image、code_block)、visual_content(展示内容)、duration(预估时长)。这里有个经验:不要让智能体直接输出时间戳,因为它对时间的感知不准确。让它输出字数,然后由程序根据语速换算成时长,这样更可靠。

[ { "narration": "公众号文章转视频,最大的坑是分镜切分。", "visual_type": "text_card", "visual_content": "分镜切分是核心难点", "duration": 4.5 }, { "narration": "用智能体做语义理解,比正则表达式靠谱得多。", "visual_type": "text_card", "visual_content": "智能体语义理解 > 正则匹配", "duration": 5.0 } ]

时长换算的公式是:duration = 字数 / 语速。中文 TTS 的默认语速大约是每秒 4 到 5 个字,我一般按 4.5 字/秒来算,再乘以 1.2 的系数留出停顿和转场时间。比如 20 个字的解说词,时长大约是20 / 4.5 * 1.2 ≈ 5.3秒。

智能体的 prompt 里还需要注入一些“风格指令”。比如“用口语化的短句”、“避免专业术语堆砌”、“每句话都要有信息增量”。这些指令会显著影响生成质量。我试过不加风格指令的版本,生成的解说词像论文摘要,读起来非常生硬。加上之后,输出明显更接近真人博主的语气。

实操心得:智能体的 temperature 参数建议设在 0.3 到 0.5 之间。太低会导致输出过于保守,每个分镜都差不多;太高会跑偏,生成和原文无关的内容。0.4 是我实测下来比较平衡的值。

还有一个容易被忽略的点是分镜的视觉多样性。如果所有分镜都是text_card,视频会非常单调。我会在 prompt 里要求智能体至少包含一个image类型(如果原文有配图)和一个code_block类型(如果原文有代码)。如果原文既没有配图也没有代码,就要求它生成一个quote_card类型,用来展示原文中的金句。

4. 语音合成与 whisper 字幕对齐的配合

语音合成这一步,我试过不少方案。早期用系统自带的 TTS,音质太机械,视频号上完播率很低。后来换成神经网络 TTS,音质上来了,但批量合成的成本也上去了。目前的方案是用本地部署的 TTS 模型做批量合成,用云端 TTS 做重点视频的精修。

本地 TTS 我推荐用 VITS 系列的模型,中文效果比较自然,而且支持调节语速和音调。合成时要注意按分镜逐段合成,而不是整篇一次性合成。逐段合成的好处是每段音频独立,后续和字幕对齐时更容易定位。每段音频保存为单独的 wav 文件,命名用分镜序号,比如segment_001.wav。

# 用 ffmpeg 将多段音频合并为一条完整音轨 ffmpeg -f concat -safe 0 -i audio_list.txt -c copy full_audio.wav

audio_list.txt的内容格式是:

file 'segment_001.wav' file 'segment_002.wav' file 'segment_003.wav'

合并完成后,用 whisper 做字幕对齐。whisper 的small模型在中文识别上已经够用,而且速度比medium快很多。如果对精度要求高,可以用medium或large,但推理时间会成倍增加。我一般用small做初稿,然后人工校对一遍。

import whisper model = whisper.load_model("small") result = model.transcribe("full_audio.wav", language="zh", word_timestamps=True) # 输出带时间戳的字幕 for segment in result["segments"]: print(f"[{segment['start']:.2f} - {segment['end']:.2f}] {segment['text']}")

whisper 的输出是带时间戳的文本片段,但它的分段可能和我们的分镜不一致。比如一个分镜的解说词被 whisper 拆成了两段,或者两个分镜的解说词被合并成了一段。这时候需要做一个时间戳重映射:根据每个分镜的音频时长,把 whisper 的输出按时间范围分配到对应的分镜里。

注意:whisper 对背景音乐比较敏感。如果音频里混了 BGM,识别准确率会下降。建议先用人声干声做识别,再把 BGM 混进去。混音时 BGM 音量控制在 -20dB 左右,不要盖过人声。

字幕的样式也有讲究。视频号是竖屏,字幕放在底部三分之一处比较合适,字号要够大,在手机上看清楚。我一般用 48 到 56 号字,白色字体加黑色描边,保证在浅色和深色背景上都能看清。

5. ffmpeg 视频合成的完整命令与参数解析

视频合成是最后一步,也是最考验 ffmpeg 功力的一步。整个合成过程可以拆成三个子步骤:生成每个分镜的画面、拼接所有分镜、叠加字幕和背景音乐。

生成单个分镜的画面,根据visual_type不同,命令也不一样。text_card类型用drawtext滤镜在纯色背景上绘制文字:

ffmpeg -f lavfi -i color=c=0x1a1a2e:s=1080x1920:d=5.3 \ -vf "drawtext=text='分镜切分是核心难点':fontfile=/path/to/font.ttf:fontsize=56:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2" \ -c:v libx264 -preset fast -crf 23 segment_001.mp4

image类型用scale和pad滤镜把图片适配到竖屏尺寸:

ffmpeg -loop 1 -i input.jpg -t 5.3 \ -vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black" \ -c:v libx264 -preset fast -crf 23 segment_002.mp4

code_block类型需要等宽字体和深色背景,可以用drawtext配合box参数:

ffmpeg -f lavfi -i color=c=0x0d1117:s=1080x1920:d=6.0 \ -vf "drawtext=text='ffmpeg -i input.mp4':fontfile=/path/to/mono.ttf:fontsize=40:fontcolor=0x58a6ff:x=80:y=400:box=1:boxcolor=0x161b22:boxborderw=20" \ -c:v libx264 -preset fast -crf 23 segment_003.mp4

所有分镜生成后,用concat协议拼接:

ffmpeg -f concat -safe 0 -i video_list.txt -c copy merged_video.mp4

最后叠加字幕和背景音乐:

ffmpeg -i merged_video.mp4 -i full_audio.wav -i bgm.mp3 \ -filter_complex "[1:a]volume=1.0[voice];[2:a]volume=0.15[bgm];[voice][bgm]amix=inputs=2:duration=first[audio]" \ -vf "subtitles=subtitle.srt:force_style='FontSize=24,PrimaryColour=&HFFFFFF,OutlineColour=&H000000,Outline=2'" \ -map 0:v -map "[audio]" -c:v libx264 -preset fast -crf 23 -c:a aac -b:a 128k output.mp4

参数选择上,-crf 23是质量和体积的平衡点,再低会明显增大文件体积,再高画质会下降。-preset fast在速度和压缩率之间比较均衡,如果追求更小体积可以用slow,但渲染时间会翻倍。音频用aac编码,128kbps 对于人声来说足够清晰。

实操心得:批量渲染时,建议用-threads参数控制 CPU 占用。我一般设成物理核心数的一半,留出资源给其他任务。如果机器有 NVIDIA 显卡,可以用-c:v h264_nvenc走硬件编码,速度能快 3 到 5 倍。

6. 常见问题排查与避坑指南

这套链路跑通之后,我在实际使用中遇到过不少问题。下面整理成速查表,方便对照排查。

问题现象可能原因排查方法解决方案
视频和音频不同步分镜时长计算偏差累积检查每个分镜的实际音频时长和预估时长差异用ffprobe读取实际音频时长,重新计算分镜时长
字幕出现乱码字体文件不支持中文检查fontfile路径和字体格式换成支持中文的字体,如思源黑体或 Noto Sans CJK
合成视频体积过大CRF 值太低或码率过高用ffprobe查看视频码率提高 CRF 到 26 到 28,或降低分辨率到 720x1280
whisper 识别准确率低音频有背景噪音或 BGM单独提取人声做识别用demucs分离人声和背景音,只用人声做识别
智能体生成的分镜过少prompt 约束不够明确检查 prompt 里的时长和分镜数量要求在 prompt 里明确要求“至少 8 个分镜,不超过 15 个”
ffmpeg 渲染速度慢软件编码 CPU 占用高查看 CPU 使用率启用硬件加速,或用-preset ultrafast临时提速

除了表格里的问题,还有几个“坑”值得单独说。第一个是字体路径问题。ffmpeg 的drawtext滤镜对字体路径很敏感,路径里有空格或中文时容易报错。建议把字体文件放在纯英文路径下,比如/usr/share/fonts/。第二个是 concat 协议的文件格式。video_list.txt里的路径必须是绝对路径或相对于列表文件的路径,而且每行末尾不能有多余空格,否则会报 “No such file or directory”。

第三个坑是 whisper 的模型下载。whisper 第一次运行时会自动下载模型文件,如果网络环境不稳定,下载会中断。建议提前手动下载模型文件放到缓存目录,或者用--model_dir参数指定本地路径。small模型的体积大约是 460MB,medium大约是 1.5GB,提前下载好能省不少时间。

提示:如果批量处理时遇到 ffmpeg 进程卡死,可以用timeout命令加超时限制。比如timeout 300 ffmpeg ...,超过 5 分钟自动终止,避免单个任务阻塞整个队列。

还有一个经验是日志记录。每个分镜的生成时间、音频时长、视频时长、文件大小都建议记录到日志里。这样出问题时能快速定位是哪个环节出了偏差。我用的是 JSON Lines 格式,每行一条记录,方便后续用jq或 Python 做分析。

7. 批量生产的工程化建议

单条视频跑通之后,下一步就是批量生产。批量生产和单条生产的最大区别在于错误处理和资源管理。单条时出错了可以手动重试,批量时必须有自动重试和失败隔离机制。

我的做法是把整个流程拆成独立的任务队列。每个任务包含文章 URL、输出路径、状态(pending/running/done/failed)。用 Redis 或 SQLite 做队列存储,用多个 worker 并行消费。每个 worker 处理一个任务,失败时记录错误信息并重试,重试超过 3 次就标记为 failed,人工介入。

资源管理方面,ffmpeg 渲染是最耗资源的环节。如果同时跑太多渲染任务,CPU 会跑满,导致其他任务变慢。我一般根据机器的核心数来限制并发渲染数,比如 8 核机器最多同时跑 2 个渲染任务。TTS 合成和 whisper 识别相对轻量,可以多跑几个。

# 用信号量控制 ffmpeg 并发数 import subprocess import threading ffmpeg_semaphore = threading.Semaphore(2) def render_video(segment): with ffmpeg_semaphore: subprocess.run(["ffmpeg", ...], check=True)

另一个工程化建议是缓存中间产物。比如 TTS 合成的音频、whisper 识别的字幕、生成的分镜脚本,都缓存到本地。如果某个环节失败重试,不需要从头再来。缓存 key 可以用文章 URL 的哈希值,加上环节名称,比如abc123_tts.wav、abc123_subtitle.srt。

最后说一个关于视频号发布的细节。视频号对视频的封面和标题有要求,封面建议用 1080x1260 的尺寸,标题控制在 20 字以内。这些可以在 ffmpeg 合成时一并生成,用-ss参数截取视频的某一帧作为封面,再用drawtext叠加标题文字。发布时通过视频号的开放接口上传,接口的调用频率有限制,建议控制在每分钟 1 到 2 条。

这套链路我从最初的手动操作,到半自动化,再到现在的全自动批量生产,前后迭代了十几个版本。最大的体会是:不要追求一步到位,先把单条跑通,再逐步优化每个环节。智能体的 prompt 需要反复调试,ffmpeg 的参数需要根据实际效果调整,whisper 的模型需要根据硬件条件选择。每个环节都有优化空间,但前提是整条链路先能跑起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询