
MAVIN 要解决的核心问题不是“生成一段视频”而是“从一句提示词生成一部多镜头成片”。ECCV 2026 Oral 这个标签说明它的重点在视觉叙事模型需要理解故事、拆分镜头、保持角色一致并把多个镜头剪成一段连贯影片。对开发者来说MAVIN 最有价值的地方在于它把工作流拆成了可以实现的模块提示词解析、剧本结构化、分镜生成、单镜头视频生成、镜头拼接与一致性控制。本文不还原论文的全部实现细节而是按工程落地思路拆解这套系统并给出一条可复现的实验路径。读完以后你可以搭一个最小版本的“提示词到多镜头成片”流水线理解每个模块的输入输出、关键参数和常见坑位。1. 为什么“从提示词到成片”不是一句提示词就能解决的1.1 单镜头生成与多镜头叙事的差距很多文本生成视频工具已经能做到“给一句描述生成一段短视频”。但这类输出通常是一个镜头一段连续画面、一个景别、一个动作段落。它没有叙事结构也没有剪辑关系。用户想要的是“一个女孩在雨夜发现线索随后跑到天台最后面对反派”这种多镜头故事而不是一条 5 秒的雨夜街道视频。从技术层面看多镜头成片比单镜头生成多出几层约束结构约束多个镜头之间要有先后顺序故事线必须连贯。内容约束角色、场景、道具需要在镜头间保持一致不能每换一个镜头就换一张脸。风格约束整体色调、镜头运动、画面比例要统一不能让成片像多个素材的随意拼接。叙事约束最终输出要符合“起因、发展、高潮、结尾”的基本叙事逻辑而不是一组随机画面。MAVIN 的做法是把“一句话”先扩展成剧本再由剧本驱动视频生成。这个思路本质上是在生成视频之前先建立一层可被程序读取的故事中间表示。有了这层中间表示后续每个镜头才知道自己应该生成什么画面、承接什么上下文。1.2 MAVIN 的核心思路把提示词先变成剧本MAVIN 的完整流程可以概括为提示词 - 剧本 - 分镜 - 单镜头视频 - 成片这里的“剧本”不完全等同于人类编剧写的剧本。它更像一种结构化脚本包含故事梗概、场景列表、角色信息、镜头序列和每个镜头的视觉描述。程序拿到这个脚本之后才能逐镜头调用视频生成模型。这样做有两个明显好处降低单次生成难度。模型只需要生成一个镜头不需要直接生成整段故事生成失败时也更容易定位问题。给拼接和剪辑留下空间。每个镜头是独立生成的后续可以单独替换、重排、补帧和加转场。这也是 MAVIN 区别于“单提示词生成视频”的核心差异。它不是试图让视频模型变得更聪明而是用结构化流程把复杂任务拆成多个简单任务。1.3 技术主线提示词、剧本、分镜、成片四层结构整个系统可以分成四层层级输入输出主要负责模块提示词层用户一句提示词结构化故事意图文本理解与大模型分析剧本层结构化故事意图场景、角色、叙事线剧本生成模块分镜层场景和角色信息镜头序列、视觉描述、关键词分镜拆解模块成片层多组镜头描述多段视频与拼接后成片视频生成与剪辑模块从这个分层看MAVIN 更像一个“导演系统”而不是单纯的视频增强模型。它把自然语言理解、文本生成、视觉生成和视频处理串联起来。工程实现时每一层都可以单独替换提示词解析可以换不同的 LLM视频生成模块可以换不同的底层模型拼接模块也可以换成专业剪辑引擎。2. 先搭实验环境文本模型、视频模型与依赖版本2.1 环境要求与版本选择在跑通一条最小链路之前需要先把环境对齐。MAVIN 这类项目通常依赖一个大语言模型用于剧本生成和一个视频生成模型用于单镜头画面生成。下面是一份适合实验的环境清单依赖建议版本或方案说明Python3.10 或 3.11兼容当前多数 AI 视频与 LLM 依赖PyTorch2.1 以上视频生成模型多数基于 PyTorchTransformers4.36 以上用于加载文本模型和多模态模型Diffusers0.27 以上使用社区扩散模型时常用FFmpeg4.4 以上用于视频解码、拼接与转码LLM APIOpenAI 兼容接口或本地部署模型用于生成剧本和分镜视频生成模型可用的文本到视频服务或本地模型不同模型对显存要求差异较大如果原始项目没有给出固定版本落地前要先确认依赖版本。因为视频生成领域迭代很快老版本 Diffusers 可能不兼容新模型权重LLM 接口参数也可能不同。2.2 安装依赖创建虚拟环境后安装基础依赖python -m venv maven_env source maven_env/bin/activate pip install --upgrade pip pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 pip install transformers diffusers accelerate sentencepiece pip install openai pydantic pip install imageio imageio-ffmpeg这里安装openai是为了调用兼容 OpenAI 接口的 LLM 服务pydantic用来定义剧本数据结构imageio-ffmpeg可以辅助处理视频帧。注意不要只验证依赖能安装完成还要确认ffmpeg命令可以在终端里直接执行。许多后续问题都出在 FFmpeg 没有加入系统环境变量。检查安装结果python -c import torch; print(torch.__version__) python -c import diffusers; print(diffusers.__version__) ffmpeg -version如果视频生成模型需要下载权重还要准备足够磁盘空间并确认模型缓存目录可写。2.3 项目目录与配置文件建议按模块拆分目录避免后续扩展时所有代码堆在一起mavin_demo/ ├── config/ │ └── pipeline.yaml ├── data/ │ └── prompts.txt ├── mavin/ │ ├── __init__.py │ ├── script.py # 剧本生成 │ ├── storyboard.py # 分镜拆解 │ ├── video_gen.py # 单镜头视频生成 │ ├── assemble.py # 镜头拼接 │ └── schemas.py # 数据结构 ├── output/ │ ├── shots/ │ └── final/ └── run.py配置文件pipeline.yaml用来统一管理模型名称、API Key、输出路径和生成参数llm: base_url: http://localhost:8000/v1 api_key: local-test model: qwen2.5-7b-instruct temperature: 0.7 max_tokens: 2048 video: provider: local model_name: text-to-video-demo num_frames: 16 fps: 8 width: 640 height: 384 output: shot_dir: output/shots final_dir: output/final这里video.provider可以是本地推理服务也可以是远程 API具体由底层视频生成能力决定。示例配置用于说明思路实际项目要结合自己的服务名和参数调整。3. 核心实现把一句提示词拆成可执行剧本3.1 先用 Pydantic 定义剧本数据结构剧本结构是整个流水线的中枢。建议用Pydantic定义清楚避免后面调用模型时字段混乱。一个最小剧本可以包含故事梗概、角色、场景和视频镜头列表。# mavin/schemas.py from typing import List from pydantic import BaseModel, Field class Character(BaseModel): name: str Field(description角色名) appearance: str Field(description角色外貌描述) identity: str Field(description身份说明) class Location(BaseModel): name: str Field(description地点名称) atmosphere: str Field(description环境氛围) class Shot(BaseModel): shot_id: int Field(description镜头序号) scene: str Field(description所在场景) location: str Field(description拍摄地点) characters: List[str] Field(description镜头内出现的角色名) camera: str Field(description景别与运镜方式例如中景推镜头) action: str Field(description角色或画面主体动作) visual_prompt: str Field(description给视频模型的完整视觉提示词) duration: float Field(default3.0, description预期镜头时长秒) class VideoScript(BaseModel): title: str Field(description故事标题) logline: str Field(description一句话故事梗概) characters: List[Character] Field(description角色列表) locations: List[Location] Field(description场景列表) shots: List[Shot] Field(description镜头列表)这里每个字段都加了description目的是让大模型在生成结构化输出时理解字段含义。很多 LLM 对 JSON 输出不稳定给字段描述能显著减少乱填问题。3.2 用 LLM 把提示词扩展成剧本有了数据结构就可以写一个函数调用 LLM从用户提示词生成剧本。以 OpenAI 兼容接口为例# mavin/script.py import json from openai import OpenAI from .schemas import VideoScript def expand_to_script(user_prompt: str, config: dict) - VideoScript: client OpenAI( base_urlconfig[llm][base_url], api_keyconfig[llm][api_key], ) system_prompt 你是一个剧本生成器。用户只会给出一句故事提示词。 你的任务是把这句话扩展成一个适合多镜头视频生成的结构化剧本。 要求 1. 角色外貌描述必须具体方便后续视频模型保持一致性。 2. 镜头数量控制在 5 到 8 个每个镜头只描述一个清晰动作。 3. 每个镜头的 visual_prompt 必须完整包含场景、角色外貌、动作、镜头运动、画面风格。 4. 输出格式必须是 JSON并严格匹配下面的字段结构。 user_content f用户提示词{user_prompt}\n请生成结构化剧本。 resp client.chat.completions.create( modelconfig[llm][model], messages[ {role: system, content: system_prompt}, {role: user, content: user_content}, ], temperatureconfig[llm][temperature], max_tokensconfig[llm][max_tokens], response_format{type: json_object}, ) content resp.choices[0].message.content script_dict json.loads(content) return VideoScript.model_validate(script_dict)这段代码的关键点在于系统提示词里明确要求“镜头数量 5 到 8 个”“每个镜头只描述一个清晰动作”“visual_prompt 必须包含场景、外貌、动作、运镜、风格”。这是为了降低后续视频生成的不确定性。注意不是所有 LLM 服务都支持response_format{type: json_object}。如果不支持可以在提示词中再加一句“只输出 JSON不要输出解释文字”然后对返回文本做一次容错解析。3.3 分镜 JSON 的字段设计分镜层可以理解为剧本的“拍摄执行表”。它比剧本更偏向视觉生成。一个典型的分镜 JSON 长这样{ shot_id: 1, scene: 开场, location: 雨夜街道, characters: [林晚], camera: 全景固定镜头, action: 林晚撑伞站在路灯下抬头看远处的钟楼, visual_prompt: 雨夜街道路灯泛着暖黄色光一位二十岁左右的年轻女性林晚黑色长发穿深色风衣撑透明雨伞站在路灯下抬头看向远处钟楼。全景固定镜头电影感冷色调背景与暖色灯光形成对比细节清晰画面稳定, duration: 3.5 }visual_prompt是整个流水线最重要的中间产物。它决定视频生成模型看到什么信息。如果visual_prompt缺少角色外貌镜头 2 里的角色就可能和镜头 1 长得完全不一样。在实现中建议把visual_prompt的生成规则固定下来场景 角色外貌 角色动作 镜头运动 画面风格 画质词。这样既方便调试也方便后续接入不同视频生成模型。4. 多镜头生成与成片拼接4.1 单镜头视频生成的统一接口多个视频生成模型的调用方式不同建议封装成一个统一的generate_shot接口。这样拼接模块不关心底层是本地模型还是远程 API。# mavin/video_gen.py from .schemas import Shot def generate_shot(shot: Shot, output_path: str, config: dict): provider config[video][provider] if provider local: # 这里替换成实际视频生成模型的推理代码 save_video_from_model(shot.visual_prompt, output_path, config) elif provider api: # 这里替换成远程视频生成服务的请求代码 call_remote_video_api(shot.visual_prompt, output_path, config) else: raise ValueError(funknown provider: {provider})在本地实验阶段可以先不部署视频生成模型而是用几张关键帧图替代视频验证剧本和分镜链路是否通畅。把视频生成替换成“生成第一帧画面”可以快速跑通完整流程def save_video_from_model(visual_prompt, output_path, config): # 示例用图像生成管线生成一张关键帧再转成短暂视频 from diffusers import DiffusionPipeline import torch pipe DiffusionPipeline.from_pretrained( stabilityai/stable-diffusion-2-1, torch_dtypetorch.float16, variantfp16, ) pipe.to(cuda) image pipe( promptvisual_prompt, num_inference_steps25, widthconfig[video][width], heightconfig[video][height], ).images[0] # 把图片直接写成 MP4 会缺少运动信息因此这里只是链路验证 image.save(output_path.replace(.mp4, _first_frame.png))这段代码用于验证链路不用于生产。真正的视频生成需要运动建模不能只输出一帧静态图。4.2 镜头间角色一致性约束多镜头项目里最常见的失败是角色“一人千面”。要缓解这个问题有几个工程手段可以尝试在剧本阶段固定角色外貌文本每次生成镜头提示词时重复使用这一整段描述。把角色名替换成具体外貌而不说“林晚”两个字。比如“黑色长发、深色风衣的年轻女性”。如果底层模型支持参考图可以为每个角色生成一张定妆照再在生成镜头时传入角色参考图。对生成完的视频做画面一致性检查计算同角色在不同镜头中的人脸相似度。最简单的实现是在组装visual_prompt时强制替换def build_visual_prompt(shot: Shot, character_map: dict) - str: prompt shot.visual_prompt for name, appearance in character_map.items(): prompt prompt.replace(name, appearance) return promptcharacter_map来自剧本中的characters列表。越早把名字替换成外貌描述镜头生成的一致性越好。哪怕底层模型并不支持多模态参考这条规则也能减少很多混乱。4.3 用 FFmpeg 拼接多镜头镜头全部生成后需要按顺序拼接。这里用 FFmpeg 可以完成多数基础工作。先准备一个文件列表file output/shots/shot_001.mp4 file output/shots/shot_002.mp4 file output/shots/shot_003.mp4然后执行拼接ffmpeg -f concat -safe 0 -i filelist.txt -c copy output/final/merged.mp4-c copy不会重新编码速度快但要求所有镜头有相同的编码格式、分辨率和帧率。如果镜头间分辨率不一致就必须重新编码ffmpeg -f concat -safe 0 -i filelist.txt \ -vf scale640:384,fps8,formatyuv420p \ -c:v libx264 -crf 18 -preset veryfast \ output/final/merged.mp4scale统一分辨率fps统一帧率formatyuv420p保证视频播放器兼容性。后面连接两个镜头的转场时可以再加xfade过滤器但那样需要处理滤镜图复杂度会上升适合在后续阶段扩展。4.4 验证生成的成片拼接完成后建议做三层检查镜头的顺序和时长是否符合剧本。可以用ffprobe读取每个镜头时长。每个镜头是否有完整画面没有黑帧、花屏或明显损坏。成片总时长是否符合预期不要出现某个镜头 0.1 秒闪过的情况。ffprobe -v error -show_entries formatduration -of defaultnoprint_wrappers1 output/final/merged.mp4如果总时长明显短于剧本预期说明某些镜头没有生成成功或被 FFmpeg 丢弃需要回头看单个镜头文件。5. 参数解读与效果调优5.1 关键参数速查表在实际调优时最常改动的参数集中在 LLM 和视频生成两端。参数常见值调大后影响调小后影响推荐使用场景temperature0.7剧本更有想象力但容易偏离提示词输出更稳定但可能过于保守正式生成用 0.5 到 0.7探索创意用 0.8 以上max_tokens2048能生成更长的分镜但耗时增加剧本可能被截断根据镜头数量调整镜头多则调大num_frames16视频更长但生成更慢视频片段过短拼接不自然实验用 16成片用 32 或更高fps8画面播放更流畅文件变大画面会卡顿低配环境用 8生产可用 24 或 30duration3.0 秒每个镜头信息量更足但生成失败率上升镜头切换过快叙事不完整单个镜头 3 到 5 秒比较合适width/height640x384画质更清晰但显存占用高画质模糊细节丢失实验用低分辨率生产按目标平台选择5.2 不同叙事长度下的配置建议如果故事比较简单比如“一个人走进咖啡馆看到一封来信”镜头数量控制在 4 到 6 个即可。此时max_tokens可以设置为 1500temperature设置为 0.5重点是动作准确。如果故事比较复杂比如“一个侦探追查线索最终发现幕后黑手”镜头数量可能需要 8 到 12 个。这时要调大max_tokens并且把剧情拆成多个段落每个段落分别生成剧本最后再合并避免一次生成过长内容导致结构混乱。如果目标是短视频平台成片通常单个镜头 2 到 3 秒整片 6 到 12 个镜头总时长 30 秒左右。镜头太短会让人看不清画面镜头太长又会显得节奏拖沓。5.3 镜头时长和画幅对齐多镜头拼接时时长和画幅必须统一。先定义规则所有镜头输出为同一分辨率和同一帧率。Python 侧可以用一个简单函数检查配置def check_shot_info(video_path, expected_fps8, expected_width640, expected_height384): import subprocess cmd [ ffprobe, -v, error, -select_streams, v:0, -show_entries, streamwidth,height,r_frame_rate, -of, json, video_path, ] result subprocess.run(cmd, capture_outputTrue, textTrue) print(video_path, result.stdout)如果出现某个镜头是 30fps 的 MP4另一个是 8fps 的 WebM直接拼接会失败或产生音画不同步。建议在生成阶段就固定所有参数不要在拼接阶段修复不兼容素材。6. 常见问题与排查链路6.1 生成结果只有几个镜头没有完整故事现象脚本输出里有 6 个镜头但实际生成或拼接后只有 2 到 3 段视频。排查顺序先看分镜 JSON 是否完整。打印VideoScript中的shots列表确认镜头数量。再检查每个镜头是否成功生成文件。看output/shots目录下文件数量。检查filelist.txt中的路径是否与文件实际名称一致。如果镜头文件名为shot_1.mp4而列表里写的是shot_001.mp4FFmpeg 会静默跳过。查看 FFmpeg 日志确认是否存在“No such file or directory”或“Invalid data found”。常见原因LLM 输出 JSON 被截断导致shots列表只有前几项。解决方法是调大max_tokens或者增加一次 JSON 合法性校验。6.2 镜头间角色不一致现象第一个镜头是黑色长发、穿风衣的女性第二个镜头变成了短发、穿夹克的人。可能原因visual_prompt里写了角色名但没写外貌描述。底层视频生成模型不稳定相同提示词在两次生成中也会出现随机差异。每个镜头使用了不同的随机种子。解决方式统一用character_map把角色名替换成详细外貌描述。固定随机种子或在调用视频模型时传入相同seed。如果模型支持参考图为每个角色生成定妆照并传入。这属于多镜头生成里最难解决的问题之一。即便模型能力有限工程上也可以通过对角色外貌做统一描述来降低偶发差异。6.3 拼接后画面跳动或转场生硬现象镜头明明顺序正确但两个镜头之间反差巨大观众会感觉像“跳了一下”。原因相邻镜头在亮度、色调、景别上差距太大。比如镜头 1 是深夜户外镜头 2 是白天室内拼接时没有过渡。处理方案在剧本阶段让 LLM 输出镜头时遵循相邻镜头画面渐变原则尽量不出现极端反差。在拼接阶段加入交叉溶解转场。FFmpeg 的xfade可以处理两个视频片段之间的过渡。如果镜头太多先用 2 秒黑场隔离再转场但这种方式观感较差。交叉溶解示例ffmpeg -i shot_001.mp4 -i shot_002.mp4 \ -filter_complex [0:v][1:v]xfadetransitionfade:duration0.5:offset2.5[v] \ -map [v] -c:v libx264 -crf 18 output/final/fade.mp4这里的offset需要根据第一个视频的时长计算通常取第一个视频的总帧数减过渡时长。6.4 显存不足或生成超时现象视频模型调用时报CUDA out of memory或者生成过程卡死。排查顺序用nvidia-smi查看当前显存占用。确认没有其他进程占用了 GPU。降低num_frames、width、height。使用torch.inference_mode()或torch.no_grad()节省显存。如果仍然不足关闭fp16之外的其他优化项或改用远程 API。显存不足在生产环境很常见尤其是同时跑多个镜头时。建议写一个简单的任务队列一次只跑一个镜头避免并行带来的显存峰值。7. 从论文原型到生产落地的差距7.1 学习环境与生产环境的差异实验环境可以接受“模型生成一段低分辨率视频人工挑选可用镜头”。生产环境则需要考虑更多保障。环节学习环境做法生产环境要求配置文件写在 Python 常量里外置到配置中心或环境变量视频生成本地跑通即可需要失败重试、超时熔断、并发控制内容校验人工看输出自动检测黑帧、静帧、低清晰度片段数据隔离测试数据混在一起按项目、用户、批次隔离日志print 输出结构化日志记录 prompt、模型版本、耗时、失败原因一致性人工观察人脸相似度计算、角色命中率统计扩展性单机脚本多机任务队列、对象存储、模型网关生产环境不能只验证“能出片”还要验证异常分支模型服务挂了怎么办、生成脏数据怎么办、用户提示词包含违规内容怎么办。这些都属于上线前必须处理的问题。7.2 可复用上线前检查清单在把 MAVIN 类流水线部署到真实服务前建议按下面清单逐项检查[ ] 提示词输入是否有长度限制和恶意内容过滤。[ ] LLM 输出是否做过 JSON 解析校验解析失败是否有重试。[ ] 每个镜头生成任务是否有超时时间和失败重试机制。[ ] 镜头文件是否存在分辨率、帧率、时长是否满足要求。[ ] 拼接前是否检查所有视频编码格式一致。[ ] 成片是否包含自动质检黑帧检测、静音检测、画面重复检测。[ ] 是否记录了模型版本、提示词版本、参数版本方便回放问题。[ ] 用户提示词与生成结果是否有审计日志。这份清单同样适用于任何“LLM 生成中间结构 多模态模型生成内容”的流水线。不要等线上出了问题再回头补。7.3 下一步扩展方向MAVIN 的最小链路已经覆盖了提示词、剧本、分镜、生成、拼接五个模块。下一步可以从三个方向扩展增加角色锁定能力。为每个角色生成定妆照并在视频生成时作为参考图输入这是提升一致性的关键路径。增加自动化剪辑。根据剧本节奏计算镜头时长自动加转场、配乐和字幕。增加叙事评估模块。用多模态模型判断成片是否包含用户提示词中的核心要素对缺失要素自动重生成对应镜头。从实践角度看最值得优先投入的是分镜质量。分镜越清楚后续视频生成和拼接的返工就越少。MAVIN 给普通开发者最重要的启示不是“某个模型有多强”而是“先用中间结构把复杂任务拆开再逐步攻破每个子任务”。这也是一条可以在自己项目里复用的方法论先定义清晰的输入输出再让模型去填充细节。