ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LLM驱动的可编程短视频管线:结构化输出与FFmpeg合成实践

LLM驱动的可编程短视频管线:结构化输出与FFmpeg合成实践 1. 从AI 生成视频到AI 辅助流水线我为什么要换思路先交代一下背景。我做知识科普类短视频账号每周要稳定产出五六条内容以产品原理、概念解释、技能教学为主。最早我也跟风用过输入一句话直接生成视频的 AI 工具生成的画面确实华丽但量产的时候就发现一个致命问题——不可控。同一句提示词跑三遍能出来三个完全不同的成品风格、节奏、配乐、旁白口音全在变时长也没法卡准。这对要批量出片、还要保持账号调性统一的人来说基本等于掷骰子。后来我换了个思路与其让 AI 一步到位生成整条视频不如把做一条短视频这件事拆成一道可编程短视频管线——从选题、脚本、分镜、配音、到字幕和成片合成每个环节都是一个可以被单独调用、单独测试、单独替换的函数。LLM 在这个管线里只负责它最擅长的部分内容决策和文案生成。画面拼接、时间轴对齐、字幕压制、转场特效这些确定性的工作全部交给代码和 FFmpeg 去完成。这个方案落地之后我单条视频的制作时间从手动剪辑的两小时左右压缩到了三分钟左右其中大部分还是等待模型返回的延迟批量十条也基本不用人盯着。这篇文章不会讲什么晦涩的算法核心是把这条管线的架构、每个节点的实现方式、以及我在实测中踩过的坑完整拆出来。适合两类人看一类是长期自己产短视频、想把手动流程自动化的人另一类是刚接触 LLM 应用开发想知道大模型落地到一个真实项目而不是聊天机器人 Demo到底是什么样的开发者。先说清楚一个心态上的转变LLM 做不好像素级的内容生成但它对一段话该怎么组织、这个场景该有什么画面感、这条视频该用什么节奏这类语言层面的判断完成度已经相当高。所以管线的设计原则很简单——让 LLM 说话让代码做事。2. 管线架构LLM 只做四个决策点其余全部代码控制2.1 四个 LLM 决策点我把整条管线拆成了六个阶段其中只有四个阶段会调用 LLM而且每次调用的目标非常单一阶段执行者输入输出脚本生成LLM选题关键词 风格配置结构化 JSON标题、钩子、三个要点、结尾引导分镜设计LLM脚本 JSON场景数组每个场景的画面类型、时长、字幕文案配音文案LLM脚本 JSON 场景数口播稿全文按停顿点分段素材匹配确定性代码分镜场景数组本地素材库中的图片/视频片段路径字幕与合成确定性代码 FFmpeg配音文本 音频时间轴 场景数组字幕文件 成片 MP4四个 LLM 节点分别是脚本、分镜、配音文案以及一个需要单独说的剪辑参数。严格来说剪辑参数我并没有单独调一次模型而是在分镜输出里附带了一组节奏指令字段比如转场类型编号、重点强调词、是否放大画面它和下文的 FFmpeg 模板配合使用。这样设计的理由很直接每多一次 LLM 调用就多一次延迟、多一笔 token 开销、多一个可能的失败点。能合并进同一个 JSON 的输出绝不给模型第二次发挥的机会。2.2 确定性代码负责什么剩下所有环节都是纯代码。素材匹配这个环节最值得展开我的素材库是一个按主题分类的本地目录代码根据分镜输出的画面标签例如芯片工厂数据图表去目录里做文件名和关键词模糊匹配命中不到就回落到预设的通用空镜素材。整个过程没有任何智能成分但正是因为确定同样的脚本跑十次结果完全一致不会出现这次配的图和上次不一样的灵异事件。字幕和合成就更不用说了。TTS 引擎读出配音音频后代码用语音识别对齐或者直接读取 TTS 引擎返回的时间戳edge-tts 这类引擎会给出每个句子的起始时间生成 SRT 字幕然后把场景视频片段、音频、字幕、背景音乐全部交给 FFmpeg 一次性合成。所有参数都是固定模板1080x1920 竖屏、30fps、H.264 编码、统一转场时长。这些参数写死在配置文件里LLM 永远碰不到。2.3 架构上最重要的一条规则这条规则是我踩了两次坑之后才总结出来的任何 LLM 输出先验证成严格的类型化数据再进入下游逻辑。绝不能把模型生成的自然语言直接拼进代码来执行。我第一次做分镜模块时偷懒让 LLM 直接输出一段描述性的操作指令文本比如在第三秒插入一个放大效果在第七秒切换到外景画面。听起来很灵活实际实现起来要写一堆自然语言解析器去猜它说的第三秒到底是第几秒、放大效果是缩放 1.2 倍还是 2 倍。更离谱的是模型偶尔会夹带私货在指令里加一句这里应该放一段震撼的音乐解析器直接崩掉。后来我彻底改成 JSON Schema 约束输出所有字段都有类型、有枚举、有取值范围下游代码不再做任何语义推断只做数值计算和文件操作。这条规则直接决定了这套管线可编程三个字成不成立。3. 让 LLM 老实输出结构化输出的工程化实践3.1 用 function calling 锁定 JSON而不是请输出 JSON让 LLM 输出结构化内容最常见的土办法是在提示词里写请以 JSON 格式输出以下字段……然后祈祷它别在 JSON 前后加注释、别用中文标点当冒号。实测下来这种方式在小样本偶尔能成但跑批量的时候十个请求里总有一两个会在 JSON 外层包一层 Markdown 代码块标记或者塞一个莫名其妙的这里是我生成的结果。我用一次骂一次。正规做法是走各家模型都支持的 function calling / tool use。我们不需要真的去调用外部函数只需要把它当成强迫模型填充一个固定参数结构的机制。下面是我脚本节点的工具定义骨架{ type: function, function: { name: submit_script, description: 提交一条短视频的完整脚本结构, parameters: { type: object, properties: { title: { type: string, maxLength: 30 }, hook: { type: string, maxLength: 50, description: 开头三秒的钩子句 }, sections: { type: array, minItems: 3, maxItems: 5, items: { type: object, properties: { point: { type: string, maxLength: 80 }, word_count: { type: integer, minimum: 30, maximum: 120 } }, required: [point, word_count] } }, cta: { type: string, maxLength: 30 } }, required: [title, hook, sections, cta] } } }调用的时候把这段 tools 参数传给 API模型会被强制返回一个tool_calls结构里面的arguments就是严格合法的 JSON 字符串。Python 端用json.loads解析后再用 Pydantic 模型做二次校验。这样双保险之后JSON 解析失败这类低级问题基本绝迹了。3.2 校验 重试把 15% 的失败率打到 3%即便有 function calling模型也可能返回语义上形状合法但内容不达标的结果比如word_count给了个 5低于 30 的下限或者sections只有 2 条。这类问题 JSON 解析发现不了必须靠业务校验。我的做法是每个节点都有一个validate_and_retry的包装函数解析 JSON → Pydantic 校验 → 如果抛错把错误信息拼接回对话上下文让模型看看报错重新生成。整个过程最多重试三次超过就放弃这一条标记为失败并记录原因。别小看这个朴素的设计它把我最初约 15% 的端到端管线失败率降到了 3% 以下。重试也不是无脑重试关键技巧是要把具体的校验错误原文塞回去。比如模型生成了word_count: 5重试时的 user 消息就要写成字段 word_count5 低于最小值 30请修正后重新提交。给模型具体报错比模糊地来一句格式不对请重试有效得多。很多 LLM 框架内置了类似能力但自己实现一遍才能理解里面那个错误反馈到底该怎么给。3.3 一个典型的 schema 报错排查过程有一段时间我调用某个 openai 兼容接口时不时报llm request failed: provider rejected the request schema or tool payload整个请求直接被拒连模型的面都见不到。这个错误外层看着像网络问题实际是服务端在对 tools 参数做严格的 JSON Schema 校验时失败了。排查了三次才定位到根因我在一个枚举字段里写了enum: [null, none, auto]把null放进了 enum 列表。部分提供商的 schema 校验器不允许 enum 中包含 null正确写法是用nullable: true配合不加 null 的 enum。另外一次是我在 properties 里给一个不存在的字段名写了 required服务端直接拒绝。这种请求层被拒的问题套路就是逐个简化 schema 字段做二分定位先拿一个最小可用的 tools 参数发请求确认通路再逐步加字段加到哪一步失败就是哪一步的问题。从那以后我养成了一个习惯新 schema 先拿一条最小调用做连通性测试再进正式管线。4. 核心节点拆解脚本、分镜、配音、剪辑参数化4.1 脚本节点控制说话的内容脚本节点是整条管线的源头它的质量直接决定后面所有环节的上限。这个节点的 prompt 模板里我不写太多花哨的话术核心是三类约束内容结构、字数预算、语态风格。结构约束就是上一节那个 JSON Schema 里的hook sections cta钩子句限 50 字以内主体拆成三到五个要点每个要点限 30 到 120 字。字数预算必须精确因为它直接推导出视频时长。我的公式是中文口播语速按每秒 4 到 5 个字估算TTS 语速设为 1.2 倍时取 5加上开头钩子留 1 秒停顿、每个要点之间留 0.5 秒过渡总时长预算就是总字数 / 5 停顿秒数。一个 300 字的脚本大约对应 65 到 70 秒的成片正好卡在短视频平台的完播线附近。语态风格是参数化配置不写在模板里。我做的是科普号风格配置是口语化、多用类比、避免术语堆砌、每段结尾埋一个悬念如果换成产品号配置会换成突出卖点、强调对比数据、直接给购买引导。这个配置以独立的 YAML 文件存在改风格不需要动代码。这个节点还有一个值得注意的细节模型很容易把要点写成对仗工整的并列句听起来像官方公告。我加的缓解手段是在 prompt 里给一个反例不要写 1. 首先……2. 其次……3. 最后……这种结构而是要求每一点都是独立的观点陈述并且至少有两点的开头用提问或否定句式。实测样本里这个反例约束比抽象描述管用得多。4.2 分镜节点把文本变成场景分镜节点接收脚本 JSON输出一个场景数组。每个场景包含五个字段scene_type枚举实拍、图片、文字卡片、数据图表、空镜、duration2 到 6 秒的整数、overlay_text叠加字幕、visual_keyword素材检索标签、emphasis布尔值是否做强调动画。映射规则是我人工定的决策逻辑不靠模型自由发挥hook 钩子句对应一个 2 秒文字卡片加背景放大动画每个要点对应一到两个场景平均字数和画面时长正相关cta 结尾固定一个品牌相关的实拍或空镜。模型需要做的其实只是在规则边界内选素材标签、定每个场景的时长、分配字幕文案。规则的活全在代码里模型的自由度被刻意压到最小。时长分配这里有个计算逻辑脚本节点给出的word_count总和换算成配音时长后分镜节点输出的场景总时长必须落在配音时长 1秒到配音时长 3秒的区间内。我会把这个约束直接写进系统提示词并在校验阶段检查超区间就触发重试。这个约束保证了视频不会有画面播完了配音还在说话的尴尬空窗。4.3 配音与字幕对齐配音节点我用的是 edge-tts 的免费接口主要原因是它返回的结果自带每个句子的时间戳字幕对齐可以白嫖到手。批量需求量大或者对音色有更严格要求的场景可以换本地部署的 VITS 系列模型但那就得自己实现一个逐字时间戳对齐模块工作量不是一个量级。字幕文件SRT的生成不是简单按文本硬切因为口播稿里经常有嗯那这样的语气词直接切开会显示嗯单独占一条字幕很蠢。我的处理是先把口播稿按标点分成句子再把 TTS 返回的句子时间戳做聚合让字幕按时长阈值合并——小于 300ms 的气口片段并入相邻字幕。这一步做完观感会从生硬字幕机变成自然跟嘴的字幕。配音和分镜的对齐还有一个隐藏问题TTS 引擎对文字的朗读时长和模型预估字数之间存在偏差尤其在数字、英文缩写密集的脚本里实际配音时长可能超预算 20%。我的解决方式是拿实际音频时长做一次后置校准音频时长确定后按比例微调每个场景的duration字段保证画面总时长始终和配音时长匹配。这个计划跟随实际修正的逻辑比反复催模型猜准字数可靠得多。4.4 剪辑指令与 FFmpeg 执行所有决策完成后代码会把场景数组翻译成 FFmpeg 命令。每条视频的渲染命令都是模板生成例如一个图片场景 缩放动画 文字叠加的最小片段长这样ffmpeg -y -loop 1 -i image.jpg -f lavfi -i colorc0x101820:s1080x1920:d2 \ -vf scale1080:1920,zoompanzmin(zoom0.001,1.2):d60:xiw/2-(iw/zoom/2):yih/2-(ih/zoom/2),drawtexttext关键词:fontsize48:x(w-text_w)/2:yh*0.75 \ -t 2 -c:v libx264 -pix_fmt yuv420p segment_001.mp4然后所有 segment 片段按顺序 concat最后再叠一遍配音、BGM 和字幕文件整体输出。FFmpeg 的 concat filter 在这里比 concat demuxer 更合适因为各片段的编码参数可能不完全一致filter 会强制统一转码避免花屏或音画不同步。渲染耗时对一条 60 秒的视频基本在 2 秒以内比起等模型返回的时间可以忽略不计。这里必须重复一次那条铁律FFmpeg 命令永远由代码模板拼出来LLM 只提供参数值。我早期天真地让模型直接给出完整 ffmpeg 命令结果它漏了音频流导致成片静音还把-c:v写成-vcodec虽然有些场景能跑但维护这种模型随机生成的命令串纯属给自己埋雷。5. 实测数据与翻车记录从手动两小时到管线三分钟5.1 效果对比数据我把这套管线跑了两个月针对科普口播类视频做了个简单对比指标手动流程管线流程单条制作耗时90-120 分钟3-5 分钟批量 10 条耗时需要全程盯约一天约 30 分钟中途只需看一眼画面风格一致性靠手感不稳定完全一致同一套模板字幕准确率手动核对TTS 时间戳对齐基本零出错成片时长误差±15 秒±2 秒最明显的变化还不是省时间而是可预期。手动剪辑状态下灵感来了剪得快状态差的时候拖半天管线状态下同一个选题给我的是完全确定性的交付——打开脚本跑一遍说三分钟出来就是三分钟出来这对我这种要同时维护多个账号的产出型选手太重要了。5.2 成本是怎么压下来的成本分两块说。LLM 调用这块我用的是一家便宜的中小模型厂商的 openai 兼容接口单条视频四个节点的总 token 消耗大概在 2500 到 3500 之间折算下来每条约 0.05 到 0.15 元人民币。为什么不用最强的旗舰模型因为这条管线的四个节点没有一个是需要深度推理的脚本生成属于中等创作能力分镜和配音稿属于规则填充编辑参数更是纯结构化输出。为了每一条多花十几倍的成本去换一个文采略好一点的脚本对量产场景来说性价比极低。TTS 用免费接口素材库图片素材是账号自己积累的FFmpeg 和运行服务器都是已有资源。所以单条约 0.2 元以内的成本里绝大部分还是模型 API。后续如果想再压可以把脚本生成切成更小的模型或者对常规模板做缓存同一个选题的相似脚本不重复调用。5.3 三次典型翻车和修复第一次翻车是 schema 请求被拒就是前面说的llm request failed: provider rejected the request schema or tool payload。那次排查花了我半天后来发现是 enum 里的 null 惹的祸。教训就是新 schema 先发最小请求验证不要在几十个字段全部写好后一次性上线。第二次翻车是音频比画面长。某个带大量英文术语的科普脚本配音实际时长比预估多了 8 秒而分镜场景总时长是按预估字数算的结果视频最后 8 秒画面停在黑屏上。这个问题的修复就是前面提到的后置校准逻辑——拿到真实音频时长后按比例把每个场景的时长拉长。但那次之后我也意识到另一个隐患拉长场景时长会导致叠字幕文本与画面内容错位字幕是场景自带的所以校准优先级应该是先调场景时长再调字幕停留时间后者跟着前者走。第三次翻车比较隐蔽素材匹配命中了主题相似但画面内容不合适的文件。比如芯片标签匹配到了一张宏观的电路板夜景图画面里还有明显的 LOGO 水印。这种问题靠关键词匹配无法根治后来的缓解办法是给素材库里的文件写了一个简单的 CSV 标签表人工标注了可用慎用两级代码优先检索可用标签。这套人工标注花了两个小时换来的是成片里出现不和谐画面的概率大幅下降。想完全消灭这类问题就得接入一个轻量的 CLIP 模型做图像语义匹配但那就是另一个项目的活了对当前产量需求来说人工标注足够。6. 能复制到什么场景边界、扩展与后续6.1 适合与不适合的场景这套管线能复制的核心前提是视频的骨架是话而不是戏。口播类、知识科普、产品卖点介绍、课程要点切片、资讯快报、招聘启事这些内容形态的本质是一段结构化的文本配上画面恰好是这条管线的舒适区。反过来剧情类短片、需要真人出镜表演、对镜头美学有极高要求广告级运镜、调色的内容这套方案完全帮不上忙——LLM 生成的分镜决策再准确也没法替代演员、灯光和摄影。还有一个容易被忽略的场景判断标准内容可以被结构化吗如果一个视频的核心价值在于画面本身比如旅行风光、美食制作过程那脚本 分镜 配音这套语言决策链路根本不适用因为观众要看的不是说了什么而是拍到了什么。这类内容应该走的是素材采集流程的自动化而不是内容生成流程的自动化。6.2 扩展RAG 知识库接入我目前正在做的扩展是给脚本节点接一个知识库也就是把这套管线从根据 prompt 自由发挥升级成根据可信资料生成。原理不复杂选题确定后先用关键词到本地知识库文档、笔记、历史视频文案里检索出相关段落把检索片段作为上下文塞给脚本节点让它基于素材写脚本而不是凭空写。这个扩展解决的是科普账号最大的风险——事实错误。纯粹靠模型记忆写脚本很容易在一本正经地讲错概念。接上检索之后即使答案没那么生动至少每个论据都能追溯到一个来源。相关热搜里的 RAG、GraphRAG 这类方案都有现成框架可以参考但从小白角度说拉一个最简单的向量检索配上 prompt 拼接就能解决 80% 的事实对齐问题不需要一上来就上多复杂的图谱框架。另一个我想做的扩展是把剪辑参数独立成一个可选的 LLM 节点面向更复杂的账号需求。比如某些场景希望根据脚本情绪自动选 BGM 风格或转场节奏目前是写死的模板未来可以让模型在受到严格枚举约束的情况下参与决策。前提仍然是输出必须是枚举值绝不能开放自由文本。6.3 一点个人体会最后分享一个实践中获得的小经验我给每个节点都加了一个dry_run参数。开启后节点只做校验和产出中间文件不调用下游渲染最后还会打印一份本节点输出摘要。这个开关让整个管线每个环节都能够独立预览和排查不用每次为了检查一个 JSON 字段把整条视频渲染出来。我把所有中间产物脚本、分镜、配音稿、指令参数)按视频 ID 落盘存放调试时直接看对应目录下的文件比看日志高效得多。在很短的时间里短视频生产会被更多人当成一个软件工程问题来处理而不是一个创意灵感问题。可编程管线本质上就是在做这件事它不负责给你灵感它负责把你已经验证过的创作方法变成一条稳定、可复用、可衡量的流水线。如果你的内容也是以话为骨架的形态这套思路大概率能直接迁移过去。哪怕只先做脚本节点和配音节点两个环节把最耗精力的部分先自动化就已经值回所有搭建成本了。
返回列表