本地素材AI智能混剪,最难的不是拼接

做了个本地素材一键混剪工具,输入一批视频素材就输出带货成片。拼接和调色不难,难的是让 AI 说的每个卖点都有画面证据支撑——做不到就直接阻断输出,不用无关画面凑时长。


手头有一堆拍好的产品视频素材,想剪一条 30 秒的抖音广告。找画面、写脚本、配口播、加字幕、调色、配乐,一晚上就没了。

我做了个工具,想把这件事压成一条命令:

python one_click_create.py --local-assets "/path/to/video-assets"

输入一批本地视频素材,工具先理解画面,再生成带货脚本、单条口播和选片计划,最后跑完整后期输出成片。也可以打开浏览器里的本地导演台操作,先看预检报告再确认生成。

真正卡住我的是另一件事:怎么让 AI 不在广告文案里编造画面里没有的卖点。

素材是真相来源,不是装饰

用 LLM 生成广告脚本时,模型会本能地补全卖点。给它一个橘子的素材,它会写出"富含维生素 C"“产地直发”“天然无添加”——但这些信息在画面里看不到,产品资料里也没有。

这不是个别现象。LLM 的训练数据里充满了广告文案,只要看到产品,它就会自动"补全"常见卖点。

所以这个项目定了一条硬规矩:

素材是真相来源:视频理解和可信产品资料决定能说什么、何时说,默认用户提供的本地素材与产品相关,但证据强度决定文案职责。

这句话不是写在 README 里做样子的。它被写进了项目的 CONTEXT.md,而且有测试用例守护——如果这段文字被人删掉,测试 test_project_context_persists_non_negotiable_local_video_principles 会直接报错。项目的六条"不可协商原则"都在同一个测试里。
在这里插入图片描述

本地混剪流水线:素材理解在先,脚本生成在后,选片受证据合同约束

证据合同:把每个卖点绑到画面上

怎么让"素材是真相来源"变成可执行的规则?答案是素材证据合同(Material Copy Contract)

核心思路是:AI 生成的每一段文案,都必须引用一个"证据锚点"(Evidence Anchor)。证据锚点有四种来源,每种能证明的事情严格区分:

  • 商品名称product:name):只证明商品身份,不能证明成分、产地、工艺或功效
  • 可信产品资料product:field:N):来自用户提供的真实产品信息,可以证明具体事实
  • 素材情境material_context:role):来自画面中可见的实体,但只能推导到有限范围——比如看到种植园可以作为产地情境,但不能继续推导具体地点
  • 视觉关联visual_relationship:N):跨来源交叉验证的实体关系

关键约束是:如果文案提出了具体产品主张(比如"富含维生素 C"),但只引用了 product:name 这个只证明身份的锚点,合同校验会直接报错:

“具体产品主张缺少相关的非名称 Evidence Anchor”

也就是说,商品叫什么名字不能证明它含什么成分。要写"富含维生素 C",要么产品资料里有这条事实,要么画面里清晰出现了相关证据。

在这里插入图片描述

证据合同校验:脚本文案通过证据合同绑定到素材证据,没有证据的主张被阻断

另一个关键设计是 LLM 只看到它应该看到的。素材理解阶段会分析每个视频窗口的画面内容,但只把 primary_visuals(主视觉)暴露给文案模型。主视觉的筛选条件很严格:prominence 必须是 primary(不是 incidental),visible_frame_count 必须 ≥ 2(至少在两帧中可见),而且最多只暴露 3 个。画面里的次要元素、一闪而过的内容,LLM 根本看不到。

这个设计有一个直接后果:如果素材里只有包装产品、没有展示原料的画面,LLM 就写不出"精选天然原料"的文案,因为它的视野里压根没有这个信息。不是告诉它"不要编",而是让它压根看不到可以编的东西。

先回答"这批素材能讲什么"

传统的混剪流程是"先写脚本再找画面"。这个项目反着来:先理解素材,再让脚本适配素材能讲什么

本地模式有一个素材预检阶段,会分析整批素材的可用容量、叙事角色和风险缺口。具体来说,它回答几个问题:这批素材总共有多少可用时长?分别能承担什么叙事角色(产品展示、使用演示、效果呈现、原料、产地等)?有没有角色缺口导致某段叙事无画面可用?

这个预检结果会直接进入脚本生成环节。LLM 拿到的是一份"素材能力清单"——包含可用角色、主视觉和证据锚点,但不包含任何窗口 ID。也就是说,LLM 知道"这批素材里有橘子的产地画面",但不知道具体是哪个文件的哪个时间段。脚本生成完后,选片阶段才真正绑定窗口。

也可以通过浏览器里的本地导演台操作:

在这里插入图片描述

本地混剪导演台:从项目简报开始,素材预检先回答"这批素材能讲什么"

导演台只绑定本机回环地址,不对局域网或公网提供服务。素材预检会展示素材源、分析窗口、可用覆盖、自然主时长、叙事角色及风险缺口,并将推荐参数写入检查器供确认或调整。正式混剪期间可查看六阶段进度、原始日志并取消任务。

一条口播管到底:为什么不拼接短句

很多视频自动化工具的做法是:每段单独生成 TTS,然后拼接。这个项目没有这样做。

全视频只生成一条连续 TTS。LLM 返回一组 voiceover_cues(口播提示),项目从中确定性地推导出完整口播文本 voiceover_full,然后一次性发送给 TTS 服务。口播的真实音频时长,就是整条视频的时间轴权威。

为什么要这样做?因为分段拼接 TTS 有三个问题:

  • 语气不连续:每段独立合成,段间语气跳跃,听感割裂
  • 时间轴不一致:字幕时间轴和口播时间轴容易错位,因为拼接后的音频时长不可预测
  • 无法控制节奏:整条口播的语速分布由 TTS 决定,分段后无法全局调整

连续口播合同由两个函数强制执行:materialize_continuous_voiceover_contract() 负责从 cues 推导完整文本,validate_continuous_voiceover_contract() 负责校验完整性。如果 cue 数量和分镜数量不一致、或者 voiceover_full 不是 cues 的有序无损拼接,直接抛出 LocalAssetError 阻断输出。

选片不重复:素材不够就直接报错

选片是本地混剪的核心环节。脚本生成完后,选片阶段才真正从素材里挑窗口。核心评分函数 _score_window() 综合了五个维度:

  • 叙事角色匹配(最高 0.45 分):这段画面适合承担 hook、value、proof 还是 cta
  • 关键词语义重叠(最高 0.15 分):文案关键词和画面描述的字符 n-gram 相似度
  • 产品可见度(0.12 × 可见度):产品在画面中的清晰程度,1-5 分制
  • 运动证据:画面是否有与文案动作匹配的运动
  • 来源顺序:同一来源文件,按时间顺序使用加分(+0.08),逆序扣分(-0.10)

最终排名还会叠加叙事角色亲和度(0.25 权重)、视觉意图对齐(0.35 权重)和来源多样性惩罚(每次复用扣 0.15,最多扣 3 次)。如果所有候选窗口都低于 MIN_MATCH_SCORE = 0.70 的阈值,选片直接失败:

raise LocalAssetError(
    f"本地素材无法完整覆盖脚本段 {seg_idx}:"
    f"需要 {target_duration:.2f}s,已匹配 {covered_duration:.2f}s"
)

而且选片有三层防重复机制:

  • 区间减法:已选片段的时间范围从候选窗口中减去,只评分剩余部分
  • 重叠硬门:与已选片段重叠超过 30% 直接拒绝,评分归零
  • 来源多样性惩罚:同一来源文件被重复使用时扣分,鼓励跨素材选片

素材不足时明确失败,不会循环、拉伸或使用无关画面。这条规则写在 CONTEXT.md 的"禁止低质量兜底"原则里,也有文档明确列出:显式指定时长时,系统会尽量规划到目标时长,但不会循环同一素材、拉伸短镜头到卡顿、使用语义无关的镜头或伪造素材中不存在的场景。

在这里插入图片描述

证据约束的核心区别:不是"不要编",而是让 AI 压根看不到可以编的东西

坑点:模块导入导致 --no-llm 失效

坑点:–no-llm 参数失效,LLM 仍然被调用

触发场景: 用户传入 --no-llm 参数,期望跳过 LLM 调用走模板降级流程。但实际运行时 LLM 仍然被调用,参数看起来完全失效。

看到的现象: 控制台输出 LLM 请求日志,但 config.LLM_ENABLED 明明已经被设为 False

根本原因: llm_client.py 在模块顶层写了 from config import LLM_ENABLED。Python 的 from ... import 是值拷贝——它在导入时读取了 config.LLM_ENABLED 的值(True),之后 one_click_create.py 在运行时把 config.LLM_ENABLED = False,但 llm_client 模块里那份拷贝仍然是 True

实际处理:llm_client.py 的导入改为运行时读取——不在模块顶层 from config import LLM_ENABLED,改为在函数体内 import config 然后读 config.LLM_ENABLED。对比之下,ad_script.py 之所以正常,正是因为它在函数体内导入。

验证结果: 修复后传入 --no-llm,LLM 不再被调用,模板降级正常触发。回归测试 test_json_timeout_retries_json_mode_without_plain_request_fallback 等用例通过。

怎么避免: 凡是需要运行时动态切换的配置项,不要用 from config import X(值拷贝),改为 import config 后在运行时读 config.X(引用读取)。或者更简单:把配置读取收敛到一个函数里,所有地方统一调用。


这个坑不是凭空发现的,是写回归测试时撞上的。项目里有一份 REGRESSION_TEST_REPORT.md,记录了 11 个审计点,上面这个是第 9 个。坑本身不大,但它说明一个问题:配置热更新的边界,Python 的导入机制帮你踩过之后你才会记住。

禁止低质量兜底:什么时候该直接报错

大部分视频工具的逻辑是"先出片再说"——素材不够就循环,画面不够长就拉伸,实在不行就用无关画面凑时长。这个项目反着来。

项目定义了 LocalAssetError 异常类,在 local_asset_pipeline.py 里有 30 多个阻断点。以下情况会直接阻断不可发布成片:

  • 视觉理解未启用或素材没有有效视频窗口
  • LLM 未返回完整、合法且通过合同校验的 segments JSON
  • 具体产品主张缺少相关 Evidence Anchor
  • 脚本要求的主体、动作或状态没有足够素材容量
  • 单条口播、字幕和语义镜头时间轴无法形成一致映射
  • 请求了口播但没有生成有效音频

失败任务会保留中间产物——素材理解结果、时间轴审查文件、脚本 JSON 和 gap 报告,都在 output/ 目录里。成功任务则清理临时渲染文件,只保留最终成片、封面和审计文件。文档里明确写着:“不要通过删除证据校验或允许任意镜头兜底来绕过失败,这会重新引入字幕与画面错位。”

只从真实反馈学习,不信任自动评分

最后一个设计决策也值得一提:脚本质量的改进只来自用户真实反馈。

项目有两个反馈系统。老的 FeedbackLoop 收集自动质量评分和自动检测的问题,可以触发自动修复。但新的 ScriptFeedbackStore 只接受 source="user" 的反馈——如果传入 source="auto",直接返回 False

用户可以这样记录反馈:

python script_feedback.py \
  --video output/final/example_final.mp4 \
  --rule "字幕应该像带货文案而不是画面描述" \
  --verdict violated \
  --comment "这版只是在重复画面"

规则从 provisional(试用)变为 active(生效)的条件是:来自 2 个不同视频的用户反馈。同一个视频的多次反馈不能自我确认。测试用例 test_automatic_observations_cannot_create_script_rules 明确验证了这一点:传入 3 条自动来源的反馈,规则数仍然是 0。

为什么这么严格?因为 LLM 的质量判断本身就是不可靠的。如果让 LLM 自己评价自己的输出,它会倾向于给自己高分。用自动评分"学习"用户偏好,本质上是让模型自己训练自己——这和把考试答案交给学生自己批改没有区别。

这个项目教会我的事

做这个项目最大的收获不是"混剪跑通了",而是重新理解了一件事:AI 生成内容的质量控制,难点不在生成端,在约束端。

LLM 生成广告文案的能力已经很强了,随便给个产品名就能写出一篇看起来不错的脚本。但"看起来不错"和"可以发布"之间,隔着证据约束、合规检查、质量门和时间轴一致性这些工程问题。这些问题的共同特点是:它们需要代码来强制执行,不能靠 prompt 工程"请求"模型自觉。

如果你也在做 AI 内容生成的工具,可以先想清楚一个问题:你的系统在什么情况下会选择不输出?如果一个工具永远能产出结果,那它大概率在某些时候在用低质量内容兜底。

如果你也在做视频混剪或 AI 内容生成工具,或者对"证据约束"的思路有疑问,欢迎在评论区聊聊你遇到的坑。


🔗 GitHub 开源地址:https://github.com/briefness/dy-ad-automation,欢迎starred。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值