ARTICLE DETAIL

资讯详情

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

Agent Skill 实战:一条命令生成产品宣传片,成本趋近于零

Agent Skill 实战:一条命令生成产品宣传片,成本趋近于零 1. 先说结论这个 Skill 是怎么把宣传片成本打下来的最近我开源了一个叫product-video-skill的项目思路很简单用一个可复用的 Skill 包把“产品宣传片”从“花钱找外包、熬夜剪片子”变成“写几行配置、跑一条命令自动出片”。如果你对 Agent Skill 这个机制还不熟可以先把它理解成“给 AI Agent 装的一个专业技能包”——里面包含说明文档、脚本、模板和素材Agent 接到任务时自动加载这套流程按步骤执行而不是靠临场发挥。这个 Skill 就是专门干宣传片这一件事的。它能解决什么问题最直接的是成本问题。市面上一段 30 秒的成品宣传片外包报价少则几千多则上万用在线模板也只能拼出千篇一律的“套壳感”。而我这套方案只要你本地有 Python、FFmpeg 和一个能调用的 LLM API再加上开源字体和免版权素材就能用代码生成一条结构完整、有配音、有字幕、有转场的宣传片。整个过程花的时间大概就是“改配置 跑命令”单条视频的边际成本趋近于零。适合谁用我主要面向这几类人独立开发者要给自己的开源项目或 SaaS 工具做一条介绍短视频小团队没有专职视频设计师但又需要在发布会、官网、社媒上快速产出物料还有想批量做测评视频、课程宣传片的运营同学。如果你手上有明确的产品素材又不想被剪辑软件的学习成本劝退那这个 Skill 会比较对胃口。后面我会把完整的目录结构、核心脚本、运行流程和踩坑记录都摊开来讲尽量让一个只写过 Python 的人也能照着复现。2. 整体设计一个“专业剪辑流程”如何被拆成可执行模块2.1 设计原则让 Agent 产出标准化工作流而不是自由发挥宣传片虽然看起来是创意活但它的内部结构其实非常模板化开场钩子、产品痛点、核心功能、使用场景、价值总结、行动号召这是不管什么产品都逃不掉的六个节点。既然有规律就能标准化。所以我没有让 Agent 从零“设计”一部片子而是先定义好分镜模板再让 Agent 负责往模板里填充产品文案和画面描述。这个思路是受“规则引擎 大模型”混合架构的启发规则负责稳定模型负责创造。如果完全让大模型自由发挥你会发现每次生成的视频结构都不一样有的漏了行动号召有的把产品名写错有的画面比例不符合平台要求。而把结构固定成 JSON Schema 后Agent 再怎么自由产出的分镜也能保证“该有的部分一个不少”。2.2 目录结构一个 Skill 包就是一个小型工程这是我的项目目录信我这东西长得像个小工程但每个文件职责非常明确product-video-skill/ ├── SKILL.md # Skill 入口文档Agent 首先读取这里 ├── config/ │ ├── product.yaml # 产品信息配置名称、卖点、目标人群 │ └── render.yaml # 渲染参数分辨率、帧率、字体、背景音乐 ├── scripts/ │ ├── generate_storyboard.py # 调用 LLM 生成分镜 JSON │ ├── render.py # 用 FFmpeg 把分镜渲染成视频片段 │ └── assemble.py # 拼接片段、合成字幕和配音 ├── templates/ │ ├── storyboard_template.json │ └── subtitle.ass # 字幕样式模板 └── assets/ ├── fonts/ # 开源中文字体比如思源黑体 └── bg/ # 免版权背景视频和音乐先说SKILL.md。它是整个 Skill 的“说明书”里面写了这个 Skill 的触发条件、输入输出格式、执行步骤和注意事项。Agent 在被问到“给产品生成宣传片”时会主动读取这个文件按里面的流程走。它的作用不是给你看而是给 AI Agent 看所以写法要像一份“操作手册”明确输入需要一个product.yaml文件路径明确输出产出一个 MP4 文件放在output/目录明确步骤先读取配置 - 生成分镜 - 准备素材 - 逐段渲染 - 合成明确禁忌不要使用平台不支持的字体不要生成超过指定时长的视频。2.3 为什么把渲染交给 FFmpeg而不是让 Agent 直接“产视频”大模型目前没法直接生成长视频即使能生成本也高得离谱。所以我的方案是分层处理LLM 负责“想”FFmpeg 负责“画”。LLM 生成的其实是分镜描述比如“淡蓝色科技感背景画面中央出现产品 Logo下方显示 Slogan”然后 Python 脚本把这些描述翻译成 FFmpeg 的 filter 参数最终渲染出画面。这样做还有个好处FFmpeg 是纯本地计算不消耗 API token也不受生成模型的速率限制。视频渲染的每一帧都是确定性的不会出现“同一段文案两次生成画面不一样”的问题。对于产品宣传片来说确定性很重要因为你可能改了文案后需要重新出片如果每帧都随机生成那根本没法做版本管理。3. 核心实现从产品信息到分镜脚本的关键代码3.1 产品配置把“人话”写成 YAML一切从配置开始。我用一个product.yaml描述产品的基本信息这个文件也是整个 Skill 的唯一“必须手写”的东西。它的设计刻意做得很“懒人友好”不需要任何剪辑知识product: name: PicKit slogan: 让产品截图变成营销素材 description: PicKit 是一个开源的截图美化工具支持自动抠图、背景替换、批量导出。 target_users: [独立开发者, 市场运营, 电商卖家] pain_points: - 产品截图太丑放官网没质感 - 设计软件学习成本高 highlights: - title: 自动抠图 desc: 上传截图即可去除纯色背景 - title: 智能排版 desc: 内置 20 套营销模板 call_to_action: 前往 GitHub 免费下载 style: primary_color: #3B82F6 background: tech-gradient tone: professional注意highlights这个字段它是分镜脚本的核心素材。每个 highlight 会成为视频里的一个独立分镜。我在模板里限定了 3 到 5 个因为 30 秒左右的宣传片塞太多信息观众根本记不住。你只需要维护这份配置剩下的脏活累活都交给脚本处理。3.2 分镜生成让大模型按模板输出 JSON接下来是generate_storyboard.py。这个脚本做的事情说人话就是把product.yaml的内容拼成一段 Prompt发给支持 OpenAI Chat Completions 协议的 API让它严格按照storyboard_template.json的格式返回一个分镜 JSON。为了稳我不会让模型直接输出自由文本而是先定义一个 JSON Schema并且在请求里明确要求“只输出 JSON不要解释”。模型返回后脚本还会做一次本地校验检查必填字段是否存在缺了就直接报错而不是带着残缺数据往下跑。import json import sys import yaml from openai import OpenAI def load_config(path: str) - dict: with open(path, r, encodingutf-8) as f: return yaml.safe_load(f) def build_prompt(config: dict) - str: return f 你是产品短视频策划。请根据以下产品信息生成一段 30 秒宣传片的分镜脚本。 要求每个分镜包含 id、duration、visual、voiceover、subtitle 字段。 visual 是对画面的视觉描述会交给 FFmpeg 渲染请描述颜色、构图、动效。 voiceover 是配音文本subtitle 是屏幕字幕subtitle 要短于 voiceover。 产品信息 {json.dumps(config, ensure_asciiFalse, indent2)} 只输出 JSON 数组不要输出任何其他内容。 .strip() def generate_storyboard(config: dict, api_key: str, base_url: str) - list: client OpenAI(api_keyapi_key, base_urlbase_url) resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: build_prompt(config)}], response_format{type: json_object}, ) raw resp.choices[0].message.content data json.loads(raw) scenes data[scenes] if isinstance(data, dict) else data validate_scenes(scenes) return scenes def validate_scenes(scenes: list) - None: required {id, duration, visual, voiceover, subtitle} for scene in scenes: missing required - set(scene.keys()) if missing: raise ValueError(f分镜缺少字段: {missing}) if __name__ __main__: cfg load_config(sys.argv[1]) api_key sys.argv[2] base_url sys.argv[3] scenes generate_storyboard(cfg, api_key, base_url) with open(output/storyboard.json, w, encodingutf-8) as f: json.dump(scenes, f, ensure_asciiFalse, indent2)这里有个很关键的经验response_format{type: json_object}不是所有 API 都支持如果你的模型服务不支持这个参数就去掉它然后让脚本做容错解析——把返回文本里第一对{...}或[...]截取出来再json.loads。我在项目里两种方式都做了兼容宁可多写 20 行防御代码也不要半夜被用户的乱数据搞崩。3.3 渲染脚本把“视觉描述”翻译成 FFmpeg 滤镜render.py是整个 Skill 里工程量最大的一块。它不是简单调一次 FFmpeg而是会遍历分镜给每个分镜生成一个独立的短视频片段每个片段再根据不同场景类型套用不同的视觉模板。我在项目内置了三种视觉模板产品高亮模板渐变背景 产品截图从中间放大入场 下方字幕条上浮痛点展示模板深色背景 大字号痛点文案居中 轻微缩放抖动行动号召模板主色背景 Slogan 放大 网址从底部滚动进入。核心是用 FFmpeg 的zoompan实现缓慢推镜再用drawtext叠加字幕最后用color和gradients源生成动态背景。下面是一个“产品高亮”片段的实际 filter 参数ffmpeg -y -loop 1 -i assets/bg/tech.jpg -i assets/img/product_shot.png \ -filter_complex [0:v]scale1920:1080,zoompanzmin(zoom0.0015,1.15):d125:xiw/2-(iw/zoom/2):yih/2-(ih/zoom/2):s1920x1080:fps25[bg]; [1:v]formatrgba,scale800:-1,zoompanzmin(zoom0.003,1.2):d125[logo]; [bg][logo]overlay(W-w)/2:(H-h)/2-60[v0]; [v0]drawtextfontfileassets/fonts/SourceHanSansCN-Bold.otf:text自动抠图:fontsize72:fontcolorwhite:x(w-text_w)/2:yh*0.68:enablebetween(t,0.5,5)[vout] -c:v libx264 -pix_fmt yuv420p output/scene_0.mp4这段命令看起来复杂拆开看其实就三层逻辑第一让背景图做缓慢推近产生“镜头在动”的感觉第二把产品截图叠在画面中央第三在字幕位置写上一句功能标题。为什么用d125因为 5 秒的分镜按 25 帧每秒算就是 125 帧d要跟分镜时长严格匹配否则最后合成出来的视频会多出几帧黑屏。另一个细节是drawtext的:enablebetween(t,0.5,5)。我故意让字幕延迟 0.5 秒出现这样观众先看到画面再看到文字视觉重心不会打架。这是剪辑里常见的“先画面后文字”原则很多人第一次写脚本会忽略结果字幕和画面同时出现观感非常生硬。3.4 合成阶段配音、字幕、背景音乐一次搞定分镜片段渲染完后assemble.py负责把它们拼起来。拼的时候不只是concat还要做三件事生成背景音乐轨道、生成配音轨道、压制字幕。配音我默认走 TTS也就是让脚本读取每个分镜的voiceover文本调用一个本地的 TTS 引擎生成 WAV。字幕用 FFmpeg 的subtitles滤镜加载 ASS 模板我会预先把字幕样式字号、颜色、位置都写在subtitle.ass里这样不会出现“系统字体不支持中文”的经典翻车。最终的合成命令大概是这样的ffmpeg -y \ -f concat -safe 0 -i output/scenes.txt \ -i output/voiceover.wav \ -i assets/bg/music.mp3 \ -filter_complex [1:a]aformatsample_fmtsfltp:sample_rates44100:channel_layoutsstereo[voice]; [2:a]aformatsample_fmtsfltp:sample_rates44100:channel_layoutsstereo,volume0.25[bgm]; [voice][bgm]amixinputs2:durationfirst[aout]; [0:v]subtitlesoutput/subtitle.ass[vout] \ -map [vout] -map [aout] \ -c:v libx264 -c:a aac -b:a 192k \ -movflags faststart \ output/final_video.mp4这里我特意把背景音乐音量压到 0.25也就是 -12dB 左右。原因很简单背景音乐是陪衬不是主角人声必须清晰。你可以在render.yaml里调这个值但我见过太多人默认音量直接盖过配音所以我干脆在模板里写死一个保守值想调再自己改。4. 实操记录跑一条命令得到一条 30 秒成品4.1 环境准备清单在真正跑通之前先把环境准备好。我建议用虚拟环境别把依赖直接装进系统 Python否则过了两个月你自己都可能忘了当时装了什么。git clone https://github.com/yourname/product-video-skill cd product-video-skill python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txtrequirements.txt里只有openai、pyyaml、pillow剩下的视频能力全靠 FFmpeg。安装 FFmpeg 这块不同系统方法不一样macOS 用brew install ffmpegUbuntu 用apt install ffmpegWindows 直接下载官方编译版并配置 PATH。装完以后执行ffmpeg -version只要能看到版本号就说明环境没问题。4.2 修改配置文件打开config/product.yaml把 PicKit 的示例内容换成你自己的产品信息。这里我建议你遵守几条规则highlights里写 3 到 5 个别超过 5 个每条 highlight 的title控制在 6 个字以内字幕显示不下slogan和call_to_action分开写slogan 是情感向的CTA 是行动向的别混在一起。我踩过一次坑把 CTA 写成了“前往官网下载并查看更多信息”结果字幕在 5 秒内根本放不下字体自动缩小到看不清。后来我把模板里 CTA 的字符数上限写死为 12超出就报错提醒。4.3 执行生成流程准备工作做完就三步python scripts/generate_storyboard.py config/product.yaml $OPENAI_API_KEY https://api.example.com/v1 python scripts/render.py output/storyboard.json python scripts/assemble.py output/第一条命令会生成output/storyboard.json你可以先打开看看 AI 写的分镜脚本靠不靠谱。如果文案风格不对就去改product.yaml里的tone字段把它从professional改成playful或minimal然后重新跑第一条命令。第二条命令把所有分镜渲染成片段耗时取决于机器性能和分镜数量我的 M 系列芯片大概 20 秒一个场景。第三条命令负责合成。如果你不想分三步跑我也提供了一个一键入口python scripts/run.py --config config/product.yaml这个run.py其实就是依次调用上面三个脚本并且在关键步骤之间加了状态检查。比如生成的故事板文件不存在时它会明确告诉你“先生成分镜”而不是在渲染阶段抛一个看不懂的 FFmpeg 报错。4.4 怎么调参数才能让视频看起来“不那么粗糙”机器生成的视频最容易露馅的地方有三个帧率、节奏和转场。帧率我建议用 25 或 30不要用 24。因为 25 帧每秒是 PAL 制基准很多在线视频平台会自动做转换24 帧在部分平台会出现微妙的不同步。转场不要一味用硬切我习惯在分镜之间留 0.5 秒交叉淡化这样观感会柔和很多。节奏方面一个分镜的时长建议控制在 3 到 6 秒少于 3 秒观众根本反应不过来多于 6 秒又会觉得拖沓。还有一个小技巧在render.yaml里把分辨率预设成 1920x1080但最终上传时如果只是发朋友圈或公众号可以再压一版 1080x1080。为什么因为竖屏或方形视频在手机上的完播率更高。渲染脚本支持canvas参数改成square_1080就会自动把背景裁剪成方形所有字幕位置也会跟着调整不会出现“画面变形但字幕还在原位置”的智熄问题。5. 常见问题速查与避坑笔记我把实际使用中遇到的高频问题整理成了表格不夸张地说这些坑我基本都亲手踩过。问题现象原因解决方案字幕全是方框/乱码系统没有中文字体或 drawtext 字体路径不对把开源字体放进 assets/fonts并在 render.yaml 指定绝对路径推荐思源黑体drawtext 报错 parse failed文案里有冒号、引号等特殊字符先转义特殊字符或者改用 subtitles 滤镜走 ASS 文件我推荐后者一劳永逸zoompan 画面抖动zoom 增量过大或 d 参数与帧数不匹配增量改成 0.0015 以内d 必须等于时长 * 帧率表达式里保留足够小数位视频没有声音配音和音乐 amix 时 duration 参数用了默认值加上durationfirst并确保两个音轨采样率一致LLM 返回的不是 JSON模型不遵守 prompt或服务不支持 response_format脚本里做容错提取找到第一个[或{截断到最后一个匹配的]或}再解析生成的画面千篇一律背景素材太少模板视觉单一多放几张背景图和视频素材或按产品行业切换背景关键词视频体积太大码率设得过高用-crf 23代替固定码率画质够用且体积小一半除了表格里的我还想再多说三个心得。第一个心得不要过度依赖 LLM 生成画面。目前的模型生成图片/视频对产品 Logo 和界面细节还原很差。我的策略是产品截图永远用真实的 PNG 素材LLM 只负责生成背景和配色方案。换句话说把“带真实信息的画面”和“纯氛围的画面”分开前者用素材后者用生成。第二个心得先测试一分半钟的片段再跑全片。我在开发时吃了大亏直接让脚本生成 6 个场景结果第三个场景的字幕字体没找到整个过程白跑。后来我在render.py里加了一个--test参数只渲染第一个分镜5 秒内就能确认字体、色彩、音频通道都没问题再跑全片。省下的时间比写这个功能的时间多得多。第三个心得版本管理要跟上。宣传片文案改一个词整个视频就要重出。我建议在output/目录里按日期建子目录比如output/20250120_v2/不要覆盖旧版本。否则客户说“还是上一版好看”你已经在原文件上反复压制了五遍根本找不回来。6. 我自己的使用体会和下一步想加的东西这个 Skill 在写完之后我先拿自己的开源项目试了一条片子。效果当然没法跟专业动画工作室比但作为产品官网首页的轮播视频、GitHub README 里的展示素材、或者发在社交媒体上的预热短视频完全够用。我个人在实际使用中最满意的点是改文案的成本几乎为零。以前视频团队改一句话可能要重新渲染半小时我这里改一行 YAML 再跑命令两分钟就出片所以我可以做很多个版本的 A/B 测试把不同 Slogan 放上去看哪条转化好。当然它目前还比较“线性”。每个分镜都是固定的视觉模板没有跳出“背景 截图 字幕”的舒适区。我下一步想给它加一个“自动收集素材”的步骤让 Agent 根据分镜脚本去指定的目录里找图片、录屏、截图按优先级顺序填进对应画面槽位。想法是把目前需要人工准备的素材环节也半自动化掉让用户只给一个很粗糙的产品介绍文档就能出片。最后再分享一个小技巧如果你在做系列视频比如每周一期产品更新那我建议把product.yaml做成一个长期维护的“产品主数据文件”每周只改highlights和call_to_action其他内容保持不变。这样你产出的所有视频在品牌色、产品介绍、背景风格上完全一致长期积累下来的素材库会形成很强的辨识度。这也是我觉得用代码生成视频最大的优势——它不追求单条片的“惊艳”但追求批量输出的“稳定”而这个优势恰好是内容团队最稀缺的东西。
返回列表