从视频到结构化 Prompt:一个多模态视频语义分析流程的工程实践
前言
随着 AI 图像生成和 AI 视频生成工具的发展,Prompt 已经不再只是简单的一句话描述。对于复杂视觉内容,尤其是视频内容,Prompt 往往需要包含主体、场景、镜头、运动、光线、色彩、风格和时间变化等信息。
在实际使用 AI 视频工具时,一个常见需求是:用户已经有一段参考视频,希望根据这段视频提取出可复用的 Prompt,用于后续的图像生成、视频生成或风格复现。
这个过程看起来像是“视频转文字描述”,但工程实现上并不只是调用一次大模型接口。视频输入包含连续帧、时间信息、场景切换、运动轨迹和视觉风格,要得到稳定可用的 Prompt,需要设计一套较完整的分析流程。
本文记录一个“视频语义分析到结构化 Prompt 生成”的工程思路,包括视频预处理、关键帧抽取、多模态理解、Prompt 结构化、异步任务和结果优化等部分。
一、问题拆解
视频转 Prompt 的核心目标不是简单生成一句标题,而是把视频里的视觉信息拆解为可复用的文本结构。
通常需要关注以下信息:
| 维度 | 说明 |
|---|---|
| Subject | 视频中的主体,如人物、物体、动物、建筑等 |
| Action | 主体正在发生的动作 |
| Scene | 场景环境,如街道、室内、森林、海边等 |
| Camera | 镜头角度、景别、运动方式 |
| Lighting | 光线方向、强弱、对比度 |
| Color | 主色调、色彩风格 |
| Motion | 人物运动、镜头运动、节奏变化 |
| Mood | 情绪氛围,如宁静、紧张、梦幻、电影感 |
| Style | 写实、动画、广告片、电影画面等风格倾向 |
如果只提取“画面里有什么”,最终 Prompt 的可用性会比较弱。对于 AI 生成任务来说,“怎么拍”“怎么动”“什么氛围”往往和“拍了什么”同样重要。
二、整体架构设计
一个相对完整的视频分析流程可以拆成以下几个阶段:
视频上传
↓
基础校验与转码
↓
关键帧抽取
↓
场景片段划分
↓
多模态模型分析
↓
结构化信息归并
↓
Prompt 生成与格式化
↓
结果展示与二次编辑
从工程角度看,这类任务有几个特点:
- 视频文件通常较大,不适合在同步请求中直接处理。
- 分析耗时不稳定,需要异步任务机制。
- 用户关心最终 Prompt,但中间分析结果也需要保留,便于调试和优化。
- 不同模型和生成平台对 Prompt 格式要求不同,需要抽象输出层。
因此,推荐将视频处理设计为异步任务,而不是普通的 HTTP 同步接口。
三、视频上传与预处理
视频上传阶段主要处理几个问题:
- 文件格式校验
- 文件大小限制
- 视频时长限制
- MIME 类型检查
- 临时存储
- 转码和压缩
- 元数据提取
常见的视频格式包括 MP4、MOV、WebM、AVI 等。为了降低后续处理复杂度,可以统一转码为标准 MP4,并限制分辨率和时长。
示例流程如下:
type VideoMetadata = {
duration: number;
width: number;
height: number;
fps: number;
format: string;
};
async function validateVideo(file: File): Promise<VideoMetadata> {
const metadata = await probeVideo(file);
if (metadata.duration > 120) {
throw new Error('Video duration exceeds limit');
}
if (file.size > 100 * 1024 * 1024) {
throw new Error('Video file is too large');
}
return metadata;
}
在真实生产环境中,建议将原始视频和处理后的视频分开存储,并为临时文件设置自动过期策略,避免存储成本不可控。
四、关键帧抽取策略
直接把完整视频交给模型分析并不总是最佳方案。视频可能包含大量重复帧,如果逐帧处理,会带来较高成本和较长等待时间。
更实用的方法是抽取关键帧。
常见策略有三种:
- 固定间隔抽帧
- 基于场景变化抽帧
- 混合策略
固定间隔抽帧实现简单,例如每 2 秒抽取 1 帧,但可能错过重要场景。基于场景变化抽帧可以优先保留切换点附近的帧,但实现和参数调优更复杂。实际项目中可以使用混合策略:固定间隔保证覆盖率,场景检测补充关键变化。
示例伪代码:
async function extractKeyframes(videoPath: string) {
const fixedFrames = await extractFramesByInterval(videoPath, 2);
const sceneFrames = await extractFramesBySceneChange(videoPath, {
threshold: 0.35,
});
return mergeAndDeduplicateFrames([
...fixedFrames,
...sceneFrames,
]);
}
在 Prompt 生成场景里,不一定需要非常高的帧率。更重要的是覆盖视频中的主要视觉变化。
五、多模态分析层
关键帧抽取后,可以将帧图像和视频元数据交给多模态模型分析。
单帧分析主要回答:
- 画面中有什么
- 主体在做什么
- 场景是什么
- 光线和色彩如何
- 构图有什么特点
- 是否存在明显风格
但视频 Prompt 还需要时间维度,因此不能只分析孤立图片。可以把帧按时间排序,并给模型提供上下文:
{
"frames": [
{
"time": "00:00",
"description": "A person stands in a neon-lit street..."
},
{
"time": "00:02",
"description": "The camera moves closer as rain reflects..."
}
],
"metadata": {
"duration": 8,
"fps": 24,
"resolution": "1920x1080"
}
}
然后让模型进一步总结:
- 视频整体主题
- 场景变化
- 镜头运动
- 主体运动
- 视觉风格
- 可用于生成模型的 Prompt
这个阶段的 Prompt 设计非常关键。模型不仅要“描述视频”,还要输出稳定结构,方便后续程序处理。
六、结构化输出设计
为了提高结果稳定性,可以要求模型输出 JSON,而不是直接输出自然语言。
示例结构:
{
"subject": "a lone person walking through a rainy city street",
"scene": "neon-lit urban alley at night",
"camera": "medium shot, slight handheld movement",
"lighting": "blue and magenta neon reflections, low-key lighting",
"color_palette": ["blue", "magenta", "black"],
"motion": "slow walking motion, subtle camera push-in",
"mood": "moody, cinematic, futuristic",
"style": "realistic cyberpunk film look",
"prompt": "A cinematic night street scene featuring..."
}
这样做有几个好处:
- 前端可以按模块展示结果。
- 用户可以单独编辑某个维度。
- 后续可以针对不同平台重新拼装 Prompt。
- 便于存储、搜索和复用。
如果直接保存一段完整文本,后续扩展会比较困难。
七、Prompt 格式化层
不同生成工具对 Prompt 的偏好不同。
例如,图像生成工具通常更关注:
- 主体
- 构图
- 光线
- 风格
- 材质
- 画质描述
视频生成工具则更关注:
- 动作连续性
- 镜头运动
- 时间变化
- 场景节奏
- 运动方向
因此可以在结构化结果之上增加格式化层。
type PromptTarget = 'image' | 'video' | 'cinematic';
function formatPrompt(data: VideoAnalysisResult, target: PromptTarget) {
if (target === 'image') {
return [
data.subject,
data.scene,
data.lighting,
data.color_palette.join(', '),
data.style,
data.mood,
].join(', ');
}
if (target === 'video') {
return [
data.subject,
data.motion,
data.camera,
data.scene,
data.lighting,
data.mood,
data.style,
].join(', ');
}
return data.prompt;
}
这样可以避免把所有需求都塞进一次模型调用中,也方便后续增加更多输出模板。
八、异步任务与状态管理
视频分析属于耗时任务,通常需要设计任务状态。
常见状态包括:
pending
processing
analyzing
formatting
completed
failed
用户上传视频后,服务端返回任务 ID。前端轮询任务状态,或者通过 WebSocket / Server-Sent Events 推送进度。
type TaskStatus =
| 'pending'
| 'processing'
| 'analyzing'
| 'completed'
| 'failed';
type VideoPromptTask = {
id: string;
status: TaskStatus;
progress: number;
result?: VideoAnalysisResult;
error?: string;
};
对于失败任务,要区分是上传问题、转码问题、模型调用问题还是结果解析问题。否则用户只能看到一个模糊的“生成失败”,排查体验会很差。
九、工程优化点
1. 控制抽帧数量
抽帧过多会增加成本,抽帧过少会影响理解效果。可以根据视频时长动态调整。
function getFrameInterval(duration: number) {
if (duration <= 10) return 1;
if (duration <= 30) return 2;
return 4;
}
2. 缓存分析结果
同一个视频重复提交时,可以通过文件 hash 命中缓存。
const hash = await calculateFileHash(file);
const cached = await findAnalysisByHash(hash);
if (cached) {
return cached;
}
3. 分阶段保存结果
不要等所有步骤完成后才写入数据库。建议在每个阶段保存中间状态,便于恢复和调试。
4. 结果解析容错
模型输出 JSON 时,偶尔会出现格式不合法的问题。可以加入修复逻辑,或者在提示词中要求严格输出 schema。
5. 隐私与清理策略
视频文件通常属于用户内容,应设置明确的清理策略。例如分析完成后自动删除源文件,只保留结构化文本结果。
十、实践验证
为了验证这个流程,我做了一个在线 Demo,用于把视频参考转换成 AI Prompt:
它的主要作用是测试“视频语义分析 → 结构化描述 → Prompt 生成”这一流程在实际创作场景中的可用性。
从结果看,这类工具更适合作为 Prompt 初稿生成器,而不是完全自动化的最终输出。用户仍然需要根据不同模型进行二次编辑,例如删除冗余描述、调整镜头语言、增加平台参数,或者把长 Prompt 改写成更适合某个模型的格式。
十一、总结
视频转 Prompt 看起来是一个简单需求,但真正实现时会涉及视频处理、多模态理解、异步任务、结构化输出和 Prompt 格式化等多个环节。
比较推荐的实现方式是:
- 不直接处理完整视频,而是先抽取关键帧。
- 不直接生成最终 Prompt,而是先生成结构化语义结果。
- 不把所有平台格式写死在模型提示词里,而是在后处理阶段做模板化拼装。
- 不把视频分析做成同步接口,而是设计成异步任务。
- 不只关注生成结果,也要保留中间状态,方便调试和优化。
从长期看,视频内容会成为 AI 生成工作流里很重要的参考输入。把视频转换为结构化 Prompt,本质上是在做一种“视觉信息到语言指令”的工程化翻译。
这个方向还有不少可优化空间,例如更细粒度的场景切分、更稳定的运动描述、更适配不同生成模型的 Prompt 模板,以及更好的用户编辑体验。