ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

用LLM搭一条可编程的短视频生产线:从脚本到成片全流程拆解

用LLM搭一条可编程的短视频生产线:从脚本到成片全流程拆解 前阵子我搭了一条能自动出片的短视频生产线核心思路就是用 LLM 把脚本、分镜、字幕、配音、剪辑这些环节串成一条“可编程的管线”。这篇文章就是来完整拆这套方案的思路是什么、结构怎么设计、代码骨架长什么样、以及我在实际运行中踩过的一堆坑。目标是让有 Python 基础、想用大模型做视频自动化的人看完能照着自己搭一套。先说清楚它到底能干什么。这套管线接收一个很粗糙的输入比如“帮我做一条讲 AI 绘画入门的短视频”然后自动完成以下工作生成解说词、把解说词拆成分镜列表、为每个分镜匹配视频素材、生成配音和字幕、最后拼接出一条带背景音乐和转场的成片。整个过程完全由代码驱动LLM 只负责中间那些需要“语义理解”的部分视频切拼这种程序逻辑则交给脚本处理。听起来很玄但落地之后你会发现真正卡人的地方其实就那么几个点这篇我都会讲到。如果你已经试过用 ChatGPT 写脚本、再用剪映手动剪片那这套管线的价值就很好理解了。它本质上是在做“无人工干预的内容生产流水线”适合做自媒体矩阵、批量科普视频、或者有大量库存素材想快速消化成视频的人。下面我按自己的实现顺序来拆解。1. 项目整体设计与思路拆解1.1 先想清楚为什么是“可编程”而不是“问AI要一条视频”很多人听到“用 LLM 做短视频”第一反应是让 AI 直接生成视频成片。但现阶段真正稳定好用的方式不是让 LLM 吐出视频文件而是让它生成“对视频制作过程的完整描述”再由程序去执行。理解这一点是整个项目成立的前提。我把 LLM 定位成一个“编导”它输出的是分镜脚本、画面描述、配音文案和字幕文本这些结构化的中间产品。真正把画面剪出来、音频合上去的是 MoviePy、FFmpeg 这类视频处理工具。为什么这么设计因为 LLM 擅长的是语言和语义视频素材的切割、拼接、缩放、编码是确定性的算法逻辑混在一起只会让两边都做不好。“可编程”在这个项目里有两层意思。第一层是流程可编排管线里的每一步都是独立函数你可以只跑脚本生成、不跑渲染也可以在渲染之前手动替换某个分镜素材。第二层是 LLM 的决策逻辑可控通过 Prompt 和参数约束让它的输出严格符合预定 schema而不是自由发挥。这两层控制住之后整个系统才谈得上稳定。1.2 管线整体架构五个模块一条数据流我最终跑通的架构是五个模块串联输入模块接收一个主题词、一篇文章链接、甚至一篇纯文本稿件统一转换成“选题描述”。脚本模块LLM 根据选题生成解说词这是整条视频的“语言层”。分镜模块LLM 把解说词切成 N 个分镜每个分镜里包含旁白文本、字幕文本、画面描述、时长建议、转场类型。素材匹配模块根据画面描述在本地素材库里检索视频片段和图片没有合适素材时用占位背景兜底。渲染模块把每个分镜的素材按时间轴拼起来叠加字幕、配音、背景音乐导出成 MP4。每个模块的输入输出都是标准 Python 数据结构主要是 dict 和 list中间结果我会落一份 JSON 到磁盘。这么做的原因很朴素方便调试、方便断点续跑调用 LLM 是有成本的如果渲染阶段挂了前面生成的东西不应该浪费。这套架构最大的优势是“可插拔”。想换 TTS 服务、想换素材免费站、想改成数字人驱动都只影响对应的一个模块管线主体不用动。这也是我坚持不把逻辑写成一坨的原因。2. 核心细节解析与实操要点2.1 脚本生成不是写作文是出结构化数据很多第一次做这类项目的人会在第一步就翻车因为他们让 LLM“生成一段脚本”。结果模型输出一篇带标题、分段、加粗文字的 Markdown程序根本没法直接消费。我的做法是让 LLM 直接输出 JSON。系统提示词里写清楚“你是短视频编导你的输出必须是一个符合指定 JSON Schema 的分镜数组禁止输出任何解释性文字”。然后给一个 few-shot 示例比如[ { scene_id: 1, narration: 很多人觉得 AI 绘画很难其实入门只需要三样东西。, subtitle: AI 绘画入门只需要三样东西, visual_prompt: 深夜书桌上放着数位屏和手绘笔屏幕亮着绘画软件界面, duration_sec: 4, transition: dissolve } ]关键在于所有字段都必须在系统提示词里提前定义好并且给出取值范围。比如transition我限定成cut、dissolve、slide_left三种因为渲染模块只实现了这三种转场LLM 一旦输出一个fade_zoom后面就报错了。宁可减少自由度也不要让下游不可控。温度参数这里我实测下来生成脚本创意部分用temperature0.9拆分子镜时用temperature0.3。原因是分镜拆解是结构化任务越冷越稳定脚本创意需要发散但一旦发散过头分镜阶段又会很痛苦。这个问题我在第 4 节还会专门讲。2.2 分镜指令设计让每个场景可执行分镜是整个管线的核心数据结构它必须同时被后期环节理解。我建议字段不要贪多够用就行以下这几个字段是经过实际验证的最小集合字段类型说明scene_idint分镜序号渲染时按此排序narrationstring该段旁白全文用于 TTS 配音subtitlestring屏幕上显示的字幕通常比旁白短visual_promptstring画面描述素材匹配模块的查询词duration_secfloat该分镜时长配音长度不够时自动延长transitionstring上一镜到本镜的转场方式设计这些字段时的核心原则是“下游能直接执行”。narration可以直接交给 TTSduration_sec可以作为画面长度的参考值visual_prompt是素材检索的 query。字段之间要有明确的对应关系不要让渲染模块再去猜语义。我当时踩过一个坑字幕字段让 LLM 自由编写它经常输出和旁白一模一样的长句结果屏幕上全是密密麻麻的字。后来我在示例里专门写了一条“字幕是旁白的提炼单行最多 12 个汉字”并在校验逻辑里强制长度超过就截断问题才解决。这类细节不亲身跑一遍真的很难意识到。2.3 素材匹配LLM 和素材库之间的“对齐层”这是整个项目里最容易被低估的模块。一开始我天真地以为给 LLM 一段画面描述然后拿这段文字去 Pexels 搜素材就行了。结果搜出来的素材和文案气质经常对不上尤其是一些抽象描述比如“希望感”或者“数据流动”基本搜不到匹配内容。后来我换了个思路不再让 LLM 自由发挥画面描述而是在 Prompt 里注入素材库的真实标签列表。比如素材库里有哪些镜头、什么场景、什么样的风格LLM 只能在这些候选标签里做组合和选择。这一步把“凭空想象画面”变成了“基于可用素材做编排”匹配成功率显著提升。具体实现上我用了一个很轻量级的方案素材入库时人工打标签存成一个 JSON 映射文件名对应标签数组。匹配函数就是简单的标签交集排序LLM 输出的visual_prompt会先做分词再和素材标签做匹配。如果你有更多工程量可以换成 CLIP 向量检索但我个人体验是对于几百条素材的小库标签匹配已经够用而且可解释性强。这个取舍后面还会再聊。3. 实操过程与核心环节实现3.1 管线代码骨架能跑通的最简版本完整代码太长了这里给一个浓缩版骨架基本能反映各个模块之间的数据流转。我平时用的调用后端是 OpenAI 兼容接口本地部署的 Ollama 也能用这套代码只需要把base_url和model换一下。import json import subprocess from pathlib import Path CACHE_DIR Path(./cache) CACHE_DIR.mkdir(exist_okTrue) def call_llm(user_prompt: str, system_prompt: str, temperature: float 0.7) - str: # 这里以 OpenAI 接口为例接入本地模型同理 from openai import OpenAI client OpenAI( base_urlhttp://localhost:11434/v1, # 本地 Ollama 示例换成其他服务也可 api_keyEMPTY, ) resp client.chat.completions.create( modelqwen2.5:14b, # 或 gpt-4o-mini 等 messages[ {role: system, content: system_prompt}, {role: user, content: user_prompt}, ], temperaturetemperature, response_format{type: json_object}, ) return resp.choices[0].message.content def generate_storyboard(topic: str) - list[dict]: system ( 你是短视频编导。将用户给出的主题拆解为 5-8 个分镜。 必须输出 JSON 对象格式为 {\scenes\: [...]}。 每个分镜包含 scene_id, narration, subtitle, visual_prompt, duration_sec, transition。 transition 只能取 cut/dissolve/slide_left。 ) user f主题{topic}\n请输出分镜 JSON。 raw call_llm(user, system, temperature0.3) data json.loads(raw) return data[scenes] def generate_script(topic: str) - str: system 你是短视频文案写手写一段 200 字左右的口播脚本语气自然不要 Markdown 标题。 raw call_llm(topic, system, temperature0.9) return raw.strip() def match_material(visual_prompt: str) - str: # 素材标签匹配返回素材文件路径简化版实现 return find_best_material_by_tags(visual_prompt) def render_video(scenes: list[dict], output_path: str): # 用 MoviePy 或 FFmpeg 按 scenes 渲染 ...这个骨架其实只体现了两个 LLM 调用点一个是把选题扩展成完整口播稿一个是把口播稿拆成分镜 JSON。很多人以为重点在渲染其实渲染反而是最成熟的部分MoviePy 文档里写得很清楚真正需要动脑子的地方是 LLM 输出的可靠性。我做了三层防护JSON 格式校验、字段缺失兜底、类型强制转换。比如duration_sec如果模型输出了字符串我会直接用float()强转转不了就改成默认值 4。3.2 一个案例跟跑从输入“AI 绘画入门”到成片我拿一个真实跑过的例子来走一遍流程。输入主题是“AI 绘画入门”第一步generate_script先产出了一段口播稿大意是“很多人觉得 AI 绘画门槛高其实只要掌握三个步骤选工具、写提示词、调参数”。这段文案大概 180 字。第二步generate_storyboard把这段文案切成了 6 个分镜。这里注意分镜化不是按字数机械切分LLM 会结合语义断点比如“选工具”是一镜“写提示词”是另一镜。每个分镜的narration加在一起应该等于前面生成的整段口播稿这是我在校验逻辑里写死的一个约束如果旁白拼接后语义对不上就重新调用分镜模块。第三步素材匹配我给每一个visual_prompt去素材库里找镜头。比如“选工具”对应的是一个电脑屏幕上打开绘画软件的画面“写提示词”对应键盘打字特写。素材库找不到的就用封面图 缓慢缩放的特效补足这招可以避免因为缺素材导致整个渲染流程中断。第四步渲染。这里我选择用 MoviePy 逐镜加载素材、按duration_sec裁剪、拼接转场再统一叠加字幕轨和 TTS 音轨。TTS 我用的是本地部署的 edge-tts免费且延迟低一小段旁白几秒钟就能生成。整个渲染过程大约 2 分钟出一条约 40 秒的视频。第一条成片的效果明显有“AI 廉价感”但胜在稳定内容逻辑通顺、字幕定位准确这一步自动化就已经产生了价值。3.3 渲染参数配置的几个经验值视频分辨率我固定成 1080x1920 竖屏这是短视频平台的主流画幅。帧率用 30码率靠 MoviePy 默认参数实测在手机上看完全够用。字幕我压在视频下方三分之一处白字黑边字体用系统中文字体尺寸大概是高度 4% 左右太大会遮挡画面太小手机上看不清。背景音乐的处理有个细节不要直接整段铺上去而是用set_audio混合时把音乐音量压低到旁白音量的 0.15 倍并且用audio_fadeout做结尾淡出。如果不压音量人声和音乐一旦同时出现会互相干扰到根本听不清。这个 0.15 倍是我多次试出来的平衡点不同音乐类型可能还要微调。4. 常见问题与排查技巧实录4.1 LLM 输出不稳定JSON 解析崩了怎么办这是跑管线最普遍的问题没有之一。即使我规定了response_format是json_object仍然会遇到模型输出内容里夹杂着解释文字、字段名被改写、JSON 里多了一个尾逗号等问题。我的解决方案不是追求模型永远正确而是做一套“解析容错 自动修复”。解析容错指的是先把返回内容里多余的解释文字清理掉提取第一个{到最后一个}之间的子串再json.loads。自动修复则是对常见错误做规则替换比如把单引号替换成双引号、去掉尾逗号。如果都失败就让 LLM 自己在提示词里看到报错信息然后重新生成最多重试三次。后来我还加了一层缓存每个 Prompt 的响应都会存在本地同一请求直接读缓存不二次调 LLM。这在调试阶段省下的时限不是一点半点因为你经常会改渲染模块的代码改完重新跑管线如果 LLM 步骤缓存住整个过程只需要几十秒就能看到渲染结果。4.2 素材匹配不准画面和解说对不上画面和文案脱节是视频观感最致命的问题。解说词在讲“提示词怎么写”画面上却在播放风景空镜这种视频一眼假。我的排查发现问题往往出在素材匹配阶段而不是 LLM 生成阶段因为visual_prompt写得太抽象素材库又没有对应标签匹配函数只能返回一个“最像但其实不像”的结果。解决办法是双管齐下。第一在 Prompt 里限制visual_prompt只能从素材库标签集中选择宁可降低画面多样性也要保证可匹配。第二给素材库做了一定程度的冗余同一个场景有多角度、多时长的片段这样即使标签相同拼接出来的画面也不至于重复到让人看腻。另外一个小经验是素材检索不要只看命中数量还要考虑时长。如果一个分镜要求 6 秒素材只有 2 秒拉伸到 6 秒观感会明显变慢。我的做法是对素材时长和duration_sec做比例判断差距超过 1.5 倍就换素材而不是硬拉。4.3 渲染阶段内存爆了怎么办MoviePy 处理视频时会把帧读进内存我一开始所有素材直接VideoFileClip()全部加载剪到第 10 个片段的时候内存占用已经将近 10GB跑长视频直接卡死。解决思路是分块处理和流式拼接。我先把每个分镜单独导出成一小段 MP4再用 FFmpeg 的 concat demuxer 把所有片段无损合到一起。这样任何时刻都只存在两个待处理文件内存占用从 10GB 降到了 500MB 以内。如果你像我一样用 MoviePy 合成字幕和音轨也可以考虑只对“需要复杂特效的那一段”用 MoviePy最终输出交给 FFmpeg。这两个工具混用才是真正的工程实践纯用 MoviePy 处理长视频就是这个项目的隐形大坑。4.4 批量出片越出越像怎么破管线跑通后下一步自然是批量生产视频。这里很快就碰到一个新问题连着生成 10 条视频选题、语气、叙事结构高度同质化看一条还行看三条就腻了。这个问题的根源不完全在 LLM而在于 Prompt 太固定。想解决就需要在脚本阶段引入随机化和风格轮换。我在系统提示词里加了一个style参数每次从一组预设风格里随机取一个比如“极简干货风”“悬疑开头风”“故事讲述风”。结构上也做一些变化有时候用“痛点开头”有时候用“场景反衬开头”让 LLM 在指定的框架内还有发挥余地。同理分镜数量和时长也不要固定。原来我固定成“5-8 个分镜每个 4 秒”后来改成“根据脚本长度动态决定时长在 3-6 秒之间浮动”成片的节奏感明显好很多也更能防止鬼打墙式的相似感。最后再分享一个经验别追求一开始就全自动。我建议先把“脚本生成 分镜拆解”自动化配音字幕渲染还用原来的人工流程跑通之后再逐步把素材匹配、渲染模块接进来。这样每一步的稳定性都能得到验证也不会因为系统太复杂导致出了问题无从下手。这条管线的价值不在于彻底不用人而在于把人从重复劳动里解放出来把时间和精力真正放到选题和风格打磨这些更有创造力的事情上。
返回列表