做 AI 短片最难受的往往不是第一张图生成得多难看而是生成到第八十个镜头时发现主角换了张脸、场景换了朝代、光线从黄昏跳回正午。尤其是做“人工智能与文明永生”这种偏概念化的主题本来就没什么现成素材可参考全靠提示词一点一点把想象拉出来一旦前后不连贯观众三秒钟就出戏。这篇文章围绕 【FOREVER :EXE】 这个短片项目完整梳理一套可复用的 AI 短片制作流程从主题拆解、分镜表设计、提示词结构化、静帧生成、图生视频、配音配乐到用 ffmpeg 统一规格、拼接、上字幕、导出成片。内容偏向工程化落地适合已经接触过图像或视频生成工具、想把零散素材做成一部完整短片的朋友完全没有接触过也没关系概念部分我会拆得比较细。读完之后你至少能拿到三样东西一套能管住几十个镜头的分镜表结构一份可直接复制的批处理与拼接脚本以及一份踩坑排查清单。1. AI 短片到底是什么概念边界与创作范式1.1 从“工具拼装”到“可复现流水线”先把概念说清楚。AI 短片并不是指“用 AI 一键生成一段视频”那种产物通常只有几秒镜头语言基本为零也没法承载叙事。工程意义上的 AI 短片是指把传统影视制作拆成若干环节——剧本、分镜、美术、摄影、配音、剪辑、调色、混音——然后用生成式工具去替代或辅助其中大部分环节最后由人力完成编排、筛选和收口。这个定义很重要因为它决定了你的工作方式。如果你的心态是“找个工具生成一下”那你做出来的东西大概率是一堆互不相关的十秒片段拼接如果你的心态是“我在做一条流水线工具只是流水线上的工位”你就会自然地去设计中间产物分镜表、提示词库、素材命名规则、规格统一脚本。前者靠运气后者可复现。我在这个项目里最大的体会是AI 短片的成品质量七成取决于流程设计三成才取决于工具本身。同一个模型有人出片像 PPT 幻灯片有人出片有电影感差别就在于是不是把“一致性”当成一个必须解决的工程问题来对待而不是指望每次抽卡都抽到好结果。1.2 为什么“文明永生”这个主题特别适合 AI 生成“人工智能 文明永生”是科幻里很经典的母题把人类文明的全部知识、语言、记忆压缩进一个可长期存续的系统里然后讨论“被保存下来的文明还算不算活着”。这类主题对 AI 短片来说其实是个甜点区原因有三个。第一它天然允许非常规的视觉。数据流、废墟、巨型档案馆、漂浮的字符、无人的城市这些画面即使有些“不真实”观众也会主动解释为风格化表达而不是穿帮。这一点大幅降低了生成难度的心理压力。第二它需要大量氛围镜头而不是复杂动作戏。动作戏是当前视频生成模型的软肋一旦涉及多人交互、快速肢体运动、手部特写失败率立刻飙升。而氛围镜头——缓慢推进、光影变化、静物特写——恰恰是图生视频最擅长的部分。第三主题抽象反而容易通过旁白建立叙事。你可以用一段克制的独白串起全部画面画面只需要承担情绪不需要承担剧情推进。这对没有真人演员、没有实拍条件的创作者来说是非常友好的结构。1.3 动手前需要建立的三个认知第一个认知一致性优先于惊艳度。单个镜头再漂亮跟前后镜头不搭就是废片。所以从第一天起就要为“角色一致性”“风格一致性”“色彩一致性”预留手段而不是生成完了再想办法调。第二个认知所有中间产物都要落盘。提示词、随机种子、参考图、生成时间、使用的模型、参数、时长全部要记录。AI 生成有很强的随机性你今天生成了一张满意的图明天想复现同款风格却复现不出来那种挫败感非常真实。所以分镜表不是给人看的文档是给脚本读的配置。第三个认知规格必须提前统一不能等到最后。不同工具导出的分辨率、帧率、编码格式、像素宽高比、音频采样率全都不一样。如果你等到剪辑阶段再统一拼接报错能把你折磨到怀疑人生。正确做法是任何素材一进项目立刻转成统一规格。2. 环境准备一条可落地的工具链2.1 硬件与系统基线AI 短片对硬件的压力主要来自两处本地推理如果跑本地模型和视频编解码一定会用到。本文示例以 Windows 11 或 macOS 作为主要工作环境Linux 同样适用命令行部分完全一致。内存建议 16GB 起步做 1080P 多轨拼接时 32GB 会从容很多。硬盘方面AI 短片项目的素材膨胀速度远超预期一个三分钟短片中间产物轻松突破几十 GB建议单独准备一块可用空间不少于 200GB 的盘。如果你不自建推理环境只使用在线的图像与视频生成服务那么硬件要求会低很多主要瓶颈变成网络和任务排队时间。这种情况下也要注意在线服务通常对导出文件的有效期有限制务必在生成后第一时间下载到本地并归档别等一周后再去点下载链接。至于具体的模型版本、服务套餐、生成额度这些变化非常快本文不做任何写死。你按自己实际能用的工具来本文重点讲的是流程和脚本工具可以替换。2.2 分层工具链每一层解决什么问题把工具链按职责分层是我认为最不容易乱的组织方式。下面这张表是 【FOREVER :EXE】 项目实际使用的分层你可以直接照着搭。层级职责典型形态是否必需剧本层主题拆解、旁白撰写、三幕结构纯文本 Markdown必需分镜层镜头清单、时长、提示词、参考图YAML 配置文件必需静帧层生成每个镜头的关键帧文生图 / 图生图服务必需动态层关键帧转视频、控制运镜图生视频服务必需音频层旁白配音、环境音、配乐语音合成 音效素材必需组装层统一规格、拼接、字幕、导出ffmpeg Python必需归档层版本管理、素材备份Git LFS / 对象存储强烈建议分层的好处是任何一层出问题你可以单独重跑那一层不用推倒重来。比如录音不满意只重跑音频层某个镜头画面崩了只重跑该镜头的静帧和动态。这种“可局部重跑”的能力是长周期项目能推进下去的关键。2.3 项目目录结构目录结构要在第一天定死后面所有脚本都依赖它。下面是本项目采用的目录树forever-exe/ ├── config/ │ ├── shots.yaml # 分镜表主配置 │ ├── style.yaml # 全局风格锚点 │ └── ffmpeg.yaml # 编码规格参数 ├── scripts/ │ ├── build_shots.py # 读取分镜表生成任务清单 │ ├── normalize.py # 调用 ffmpeg 统一规格 │ ├── concat.py # 拼接成片 │ └── make_srt.py # 生成字幕文件 ├── assets/ │ ├── refs/ # 角色与场景参考图 │ ├── raw_image/ # 原始静帧 │ ├── raw_video/ # 原始视频片段 │ └── audio/ # 旁白、音效、配乐 ├── work/ │ ├── norm/ # 统一规格后的片段 │ └── tmp/ # 临时文件 ├── output/ │ └── forever_exe_1080p.mp4 └── log/ └── gen.log # 生成记录这里有一个细节值得说明assets/raw_*目录放的是未经任何处理的原始产物work/norm放的是处理后的中间产物。为什么不直接在原始文件上处理因为原始文件是不可再生的生成一次要花钱花时间一旦你覆盖了想回退就没有了。所有加工都在副本上做这是数据安全的基本纪律。2.4 版本与素材管理约定文本类文件YAML、Python、Markdown用 Git 管理这个没有争议。但图像和视频文件体积大直接进 Git 会把仓库撑爆推荐用 Git LFS或者干脆只把“索引信息”入库素材放单独磁盘、对象存储或 NAS。不管用哪种方式命名规则必须统一。本项目采用的规则是{序号两位}_{场景代号}_{镜头号}_{版本}.{扩展名}例如03_archive_s012_v02.mp4表示第 3 场、档案馆场景、第 12 个镜头、第 2 版。这条规则看起来啰嗦但当你手上有两百多个文件、要找出“档案馆那场第 12 个镜头的最终版”时你会感谢当初的自己。版本号只增不减永远不覆盖旧版本。不满意就在 v02 基础上生成 v03v02 留着。原因很实际AI 生成存在“这一版比上一版好但下一版更差”的情况保留历史版本是唯一的后悔药。3. 核心环节拆解剧本、分镜、提示词3.1 把抽象主题拆成能“拍”的镜头“文明永生”是个宏大命题但镜头不认宏大镜头只认具体的画面。所以第一步不是写提示词而是做一次“可拍摄化”的转译把你脑子里那些概念翻译成摄影机能拍到的东西。我在这个项目里用的是“意象锚点”法。先列出 8 到 12 个核心意象每个意象对应一种视觉母题全片的镜头都从这些母题里派生。这样做的好处是视觉上天然统一不会东一榔头西一棒子。一个参考拆法母题 A被压缩的记忆——对应巨型书架、漂浮光点、缓慢收缩的数据流。母题 B无人的城市——对应空荡街道、黄昏逆光、静止的交通信号灯。母题 C机械的凝视——对应镜头、光圈、缓慢转动的环形结构。母题 D自然对人工的覆盖——对应藤蔓爬过金属、水漫过电路板。确定母题之后再写三幕结构。三分钟左右的短片比较稳的节奏是第一幕 40 秒建立世界观第二幕 100 秒展开冲突与转折第三幕 40 秒收束与留白。第三幕一定要留白AI 生成的画面信息量偏大如果结尾还在拼命塞内容观众会觉得吵。3.2 结构化分镜表用 YAML 管住全流程这是整套流程的地基。分镜表不是给人看的 Excel而是给脚本读的配置所以字段设计要兼顾“人能填”和“机器能读”。下面是本项目实际使用的config/shots.yaml结构只列了三个镜头作为示例实际项目有四十多个# 文件路径config/shots.yaml project: name: FOREVER :EXE resolution: 1920x1080 fps: 24 total_duration: 186 # 全局风格锚点所有镜头拼接此段前缀 style_anchor: cinematic still, muted teal and amber palette, volumetric haze, shallow depth of field, 35mm lens, high dynamic range, no text, no watermark # 全局负向提示词避免出现文字和畸变 negative_prompt: text, letters, watermark, logo, extra fingers, deformed hands, low resolution, jpeg artifacts, oversaturated, cartoon shots: - id: s01 scene: archive act: 1 duration: 6 subject: 巨大档案馆内部成千上万的光点在书脊间缓慢上升 prompt: vast underground archive hall, endless shelves, tiny glowing particles rising slowly between book spines, single light source from above, cold teal light with warm amber highlights camera: slow push in, dolly forward ref_image: assets/refs/archive_wide.png seed: 20240517 status: done - id: s02 scene: archive act: 1 duration: 5 subject: 特写一只手悬停在发光的书脊前 prompt: extreme close up of a human hand hovering before a glowing book spine, dust particles in the air, rim light on fingertips, shallow focus camera: static, subtle handheld shake ref_image: assets/refs/hand_ref.png seed: 20240518 status: done - id: s03 scene: city act: 1 duration: 7 subject: 无人城市黄昏交通信号灯独自闪烁 prompt: empty city intersection at dusk, traffic light cycling alone, long shadows, thin fog, no people, no vehicles, muted color grading camera: slow lateral pan, left to right ref_image: null seed: 20240519 status: pending字段说明如下id是镜头唯一标识也是文件名前缀scene用于分组方便按场景批量重跑act用于标记三幕结构剪辑时按幕排序duration是期望时长单位秒最终会被拼接脚本使用prompt只写本镜头独有的描述公共部分由style_anchor拼接ref_image是参考图路径没有则填nullseed记录随机种子用于复现status用于标记进度脚本可以只处理pending的镜头。这种设计的核心价值在于提示词的公共部分只维护一份。风格漂移最常见的成因就是每个镜头的提示词各写各的第 1 个镜头写了cinematic第 20 个镜头忘了写风格立刻就散。把公共部分抽到style_anchor里由脚本强制拼接这个问题从机制上被消灭了。3.3 提示词的四段式结构提示词写法有很多流派我在这个项目里固定用四段式因为它最容易排查问题主体段画面里有什么谁、在哪、在干什么。环境段光线、天气、时间、氛围、景深。风格段镜头焦段、色彩倾向、质感、参考媒介。约束段不要什么用于负向提示词。其中第 3 段就是style_anchor第 4 段就是negative_prompt两者全局统一。你要写的只有第 1、2 段。举个对比例子。新手容易这么写一个很有未来感的档案馆很酷这句话对模型来说几乎等于没信息。“未来感”是抽象评价不是视觉描述“很酷”更是纯主观。模型只能瞎猜结果就是每次猜得都不一样。改成四段式之后# 主体段 vast underground archive hall, endless vertical shelves # 环境段 single overhead light source, cold teal ambient with warm amber rim light, volumetric haze, dust particles suspended in air, shallow depth of field # 风格段由 style_anchor 自动拼接 cinematic still, muted teal and amber palette, 35mm lens, high dynamic range # 约束段由 negative_prompt 自动拼接 text, watermark, extra fingers, deformed hands差别在哪第二版的每个词都指向一个可以被渲染出来的视觉属性。焦段决定透视色温决定情绪雾决定空间纵深颗粒决定质感。这些词是可执行的指令不是感受。3.4 角色一致性参考图 固定描述块短片里只要出现人一致性就是头号难题。同一个角色在第 3 镜和第 25 镜长得不一样观众立刻会认为是两个人。本项目用的是“双保险”策略第一重保险是固定参考图。先专门生成一张角色定妆照正面、半身、中性光、无遮挡存到assets/refs/下。后续所有涉及该角色的镜头都用这张图做图生图的参考输入。参考图的权重不要拉到最高通常留一点空间让模型适配当前镜头的光线和角度。第二重保险是固定描述块。把角色的外貌特征写成一个不可变的文本块每次原样粘贴。这个块要足够具体# 角色描述块不可修改全片复用 a woman in her early thirties, short black hair tucked behind ears, thin silver frame glasses, a small scar on the left eyebrow, wearing a plain grey high-collar jacket, no jewelry注意这里刻意避开了“漂亮”“气质好”这类形容词全部换成可判定的物理特征发型、眼镜、疤痕位置、衣服款式。可判定的特征才能在每一帧里被稳定复现。这也是为什么很多教程强调“不要用评价性词汇”不是玄学是信息量问题。第三重保险其实是后处理如果个别镜头实在调不回来可以在剪辑阶段用轻微的统一调色把差异压下去。但这只是补救不能当主要手段。4. 完整实战三分钟短片从 0 到成片4.1 初始化项目与依赖先把目录和依赖准备好。Python 建议 3.9 以上需要PyYAML读配置、requests调服务、tqdm显示进度。ffmpeg 必须装好并加入 PATH这是后面所有视频处理的基础。# 创建目录结构 mkdir -p forever-exe/{config,scripts,assets/{refs,raw_image,raw_video,audio},work/{norm,tmp},output,log} cd forever-exe # 初始化 Git git init printf work/\nassets/raw_video/\nassets/raw_image/\noutput/\n*.mp4\n*.png\n .gitignore # 安装 Python 依赖 python -m pip install --upgrade pip python -m pip install pyyaml requests tqdm装完之后验证 ffmpeg 是否可用ffmpeg -version ffprobe -version两个命令都能输出版本信息说明环境就绪。如果提示找不到命令Windows 用户需要把 ffmpeg 的bin目录加入系统环境变量macOS 用户用包管理器安装后一般自动可用Linux 用户注意不要只装ffmpeg而漏装ffprobe有些发行版会拆成不同包。Python 的版本要求说明一下本文脚本只用到标准库加三个第三方库语法上兼容 3.9 及以上。如果你的环境是 3.8多半也能跑但建议不要更低因为后续如果引入类型标注或新语法会踩坑。4.2 生成任务清单把分镜表变成可执行队列分镜表填好之后第一步是把它转成任务清单只挑出还没做的镜头。这个脚本的职责很单一读 YAML、拼提示词、输出待办列表。它不负责调用任何生成服务这样即使你换工具这个脚本也不用改。# 文件路径scripts/build_shots.py import yaml import json from pathlib import Path ROOT Path(__file__).resolve().parent.parent CONFIG ROOT / config / shots.yaml def load_config() - dict: with open(CONFIG, r, encodingutf-8) as f: return yaml.safe_load(f) def build_full_prompt(shot: dict, style_anchor: str) - str: 将镜头独有描述与全局风格锚点拼接为完整提示词 subject .join(shot.get(prompt, ).split()) style .join(style_anchor.split()) return f{subject}, {style} def main() - None: cfg load_config() style_anchor cfg[style_anchor] negative .join(cfg[negative_prompt].split()) pending [] for shot in cfg[shots]: if shot.get(status) done: continue pending.append({ id: shot[id], scene: shot[scene], duration: shot[duration], prompt: build_full_prompt(shot, style_anchor), negative_prompt: negative, ref_image: shot.get(ref_image), seed: shot.get(seed), }) out ROOT / work / tmp / queue.json out.parent.mkdir(parentsTrue, exist_okTrue) with open(out, w, encodingutf-8) as f: json.dump(pending, f, ensure_asciiFalse, indent2) print(f待生成镜头数: {len(pending)}) print(f任务清单已写入: {out}) if __name__ __main__: main()运行方式python scripts/build_shots.py预期输出类似待生成镜头数: 38 任务清单已写入: /path/to/forever-exe/work/tmp/queue.json这个脚本有三个值得注意的设计点。第一用 .join(text.split())把多行 YAML 折叠成单行因为多数生成服务对提示词里的换行处理不一致有的会截断有的会当成两个独立提示词。第二风格锚点强制拼接不给手动跳过的机会。第三只输出pending的镜头done的直接跳过这样重跑脚本不会重复生成省钱省时间。4.3 静帧生成与素材归档拿到队列之后逐条调用你实际使用的图像生成服务。这一步各家接口差异很大我不写死任何具体 API只给出一个通用封装的骨架你把中间那段替换成自己的调用逻辑即可。# 文件路径scripts/gen_image.py import json import time from pathlib import Path ROOT Path(__file__).resolve().parent.parent QUEUE ROOT / work / tmp / queue.json OUT_DIR ROOT / assets / raw_image def call_image_service(prompt: str, negative: str, ref_image: str | None, seed: int, size: str 1920x1080) - bytes: 占位函数替换为你实际使用的图像生成服务。 返回值为图片二进制内容。 raise NotImplementedError(请替换为真实的生成服务调用) def main() - None: OUT_DIR.mkdir(parentsTrue, exist_okTrue) with open(QUEUE, r, encodingutf-8) as f: queue json.load(f) for item in queue: target OUT_DIR / f{item[id]}_v01.png if target.exists(): print(f[skip] {item[id]} 已存在) continue try: data call_image_service( promptitem[prompt], negativeitem[negative_prompt], ref_imageitem.get(ref_image), seeditem.get(seed) or 0, ) target.write_bytes(data) print(f[ok] {item[id]} - {target.name}) except NotImplementedError: raise except Exception as exc: print(f[fail] {item[id]}: {exc}) # 主动限速避免触发服务端频率限制 time.sleep(2) if __name__ __main__: main()这里的限速time.sleep(2)不是可选项。批量调用时最常见的失败原因就是触发频率限制一旦被限往往是连续失败前面的成功也白等。宁可慢一点也要稳。如果服务方明确提供了配额查询接口更稳妥的做法是每次调用前查一次剩余额度。生成完成后立刻做一轮人工筛选。不要指望批量生成完再统一看那时候你已经忘了每个镜头的意图。筛选标准就三条主体是否符合分镜描述、风格是否与前后镜头一致、有没有明显畸变手指、文字、结构错乱。不合格的标记出来调整提示词重新生成 v02原来的 v01 保留不动。4.4 图生视频与运镜控制静帧满意之后进入图生视频环节。这一步的目标不是让画面“动起来”而是让画面按你想要的节奏动。很多人失败在这里生成出来的视频确实在动但动的方式和叙事节奏完全不符比如一个应该安静的沉思镜头画面却在剧烈摇晃。所以运镜必须写进分镜表。本项目用的运镜词表比较克制只有六种运镜类型描述词适用场景时长建议缓推slow push in, dolly forward建立氛围、聚焦细节5-8 秒缓拉slow pull back, dolly out揭示环境、收尾6-10 秒横移slow lateral pan扫描空间、过渡5-7 秒静帧微动static with subtle handheld shake特写、情绪停留4-6 秒升镜slow crane up场面升级、震撼感6-9 秒环绕slow orbit around subject展示主体、强调存在感6-8 秒每种运镜都要配合时长。同样是缓推4 秒会显得仓促12 秒又会让人走神。上表的建议时长是反复试出来的经验值你可以作为起点微调。生成视频时单段时长不要贪长。目前多数图生视频工具在 5 秒左右稳定性最好超过这个长度容易出现后半段画面崩坏或内容漂移。所以策略是一个叙事镜头拆成两到三段生成剪辑时接起来。这样既保证了稳定性又给了你剪辑上的调整空间。4.5 配音、配乐与音效画面齐了之后处理声音。AI 短片的声音部分经常被忽视但它对观感的影响被严重低估——画面再精致配音是机械腔、音量忽大忽小整片立刻掉档。旁白配音的关键在于分段生成而不是整段生成。按句子拆分每句单独合成好处是某一句不满意可以只重做那一句不用整段重来坏处是句与句之间的语气连贯性会差一些。解决办法是在每句末尾补一个短停顿参数或者在剪辑时手动调整间隔。配乐建议用纯器乐避免人声否则会和旁白打架。风格上和画面统一本片用的是低频铺底加稀疏的合成器音色整体偏冷。音量一定要压得很低旁白的 -16 LUFS 左右配乐压到 -28 LUFS 以下只做氛围垫底。音效是提升质感性价比最高的部分。档案馆场景里加一点低频空间混响、光点上升时加一点细微的高频“沙沙”声、机械结构出现时加一点金属共振成本很低效果立竿见影。音效素材务必确认授权商用发布尤其注意。4.6 用 ffmpeg 统一规格并拼接这是整套流程里最“工程”的一步也是最容易翻车的一步。不同工具导出的素材分辨率、帧率、像素格式、音频采样率很可能全部不一样。直接拼接会出现三类典型问题拼接失败报错、画面拉伸变形、音画不同步。正确做法是先归一化再拼接两步分开。先看归一化。下面这条命令把任意输入视频转成 1920x1080、24fps、H.264、yuv420p、AAC 48kHz 立体声的统一规格#!/usr/bin/env bash # 文件路径scripts/normalize.sh IN_DIRassets/raw_video OUT_DIRwork/norm mkdir -p $OUT_DIR for f in $IN_DIR/*.mp4; do name$(basename $f .mp4) ffmpeg -y -i $f \ -vf scale1920:1080:force_original_aspect_ratiodecrease,pad1920:1080:(ow-iw)/2:(oh-ih)/2,fps24,setsar1 \ -c:v libx264 -preset medium -crf 18 -pix_fmt yuv420p \ -c:a aac -ar 48000 -ac 2 -b:a 192k \ -movflags faststart \ $OUT_DIR/$name.mp4 echo normalized: $name done这条命令里几个参数值得单独说。force_original_aspect_ratiodecrease保证等比缩放不拉伸pad负责补黑边居中两者配合才能既不变形又填满画幅。setsar1把像素宽高比设为 1:1这个东西不对的话播放器会二次拉伸画面看着就是“怪怪的”。-pix_fmt yuv420p是兼容性最好的像素格式不加这个参数很多播放器和平台会拒绝播放。-movflags faststart把索引信息移到文件头便于在线播放时快速起播。再看拼接。ffmpeg 的 concat 有两种模式文件列表模式和 concat 协议。这里用文件列表模式要求所有片段规格完全一致正好被上一步保证了。#!/usr/bin/env bash # 文件路径scripts/concat.sh NORM_DIRwork/norm LISTwork/tmp/list.txt OUToutput/forever_exe_1080p.mp4 # 按文件名排序生成列表序号必须补零否则 10 会排在 2 前面 ls $NORM_DIR/*.mp4 | sort $LIST sed -i s|^|file |; s|$|| $LIST ffmpeg -y -f concat -safe 0 -i $LIST \ -c copy \ -movflags faststart \ $OUT echo done: $OUTsort这一步绝对不能省。文件名如果不补零s10会排在s2前面成片顺序直接乱掉。这也是前面命名规则要求“两位序号”的原因。-c copy表示不重新编码直接复制流速度极快。但它的前提是所有片段编码参数完全一致只要有一个不一致就会失败。如果拼接时报错八成是归一化那一步漏了某个文件。4.7 字幕生成与最终导出字幕用 SRT 格式最简单也最通用。本项目没有用语音识别自动打轴而是根据分镜表的时长信息手工排了一遍因为旁白是分段合成的每段的起止时间是已知的反而不需要识别。下面这个脚本把时间轴信息转成 SRT# 文件路径scripts/make_srt.py import json from pathlib import Path ROOT Path(__file__).resolve().parent.parent CUES ROOT / config / subtitle_cues.json OUT ROOT / output / forever_exe.srt def fmt_time(seconds: float) - str: 把秒转换为 SRT 时间格式 HH:MM:SS,mmm ms_total int(round(seconds * 1000)) hours, ms_total divmod(ms_total, 3600_000) minutes, ms_total divmod(ms_total, 60_000) secs, ms divmod(ms_total, 1000) return f{hours:02d}:{minutes:02d}:{secs:02d},{ms:03d} def main() - None: with open(CUES, r, encodingutf-8) as f: cues json.load(f) lines [] for idx, cue in enumerate(cues, start1): lines.append(str(idx)) lines.append(f{fmt_time(cue[start])} -- {fmt_time(cue[end])}) lines.append(cue[text]) lines.append() OUT.parent.mkdir(parentsTrue, exist_okTrue) OUT.write_text(\n.join(lines), encodingutf-8) print(f字幕已生成: {OUT}, 共 {len(cues)} 条) if __name__ __main__: main()配套的时间轴配置长这样[ { start: 2.5, end: 7.0, text: 我们把整座图书馆压缩进了一枚芯片。 }, { start: 7.5, end: 12.0, text: 它记得所有的字却再没有人来读。 } ]最后把字幕烧进画面导出成片ffmpeg -y -i output/forever_exe_1080p.mp4 \ -vf subtitlesoutput/forever_exe.srt:force_styleFontNameSource Han Sans SC,FontSize22,PrimaryColourH00FFFFFF,OutlineColourH00000000,BorderStyle1,Outline2,Shadow0,MarginV40 \ -c:v libx264 -preset medium -crf 18 -pix_fmt yuv420p \ -c:a copy \ -movflags faststart \ output/forever_exe_final.mp4force_style里几个参数的作用FontSize是字号22 在 1080P 下清晰但不抢戏PrimaryColour是字色注意 ffmpeg 用的是 AABBGGRR 顺序不是常见的 RGBA写成H00FFFFFF是白色BorderStyle1加Outline2给文字描边保证在亮背景上也看得清MarginV40控制底边距。字体名必须是你系统里已安装的字体写错了不会报错但会静默回退到默认字体很多人就是在这里发现字幕字体怎么调都不生效。导出完成后建议至少在三种设备上各看一遍电脑播放器、手机竖屏查看、投屏或外接显示器。不同屏幕的色彩倾向和亮度差别不小暗部细节在小屏上往往直接消失这时候该提亮就得提亮。5. 常见问题与排查清单5.1 画面闪烁与角色漂移这是 AI 短片最高频的问题。表现是相邻镜头之间色调跳跃明显或者同一角色在不同镜头里脸型、发际线、年龄感发生变化。根因通常有三类。第一类是提示词不统一每个镜头各写各的风格描述这是最容易被忽略也最容易修的一类。第二类是参考图缺失或权重过低模型完全没有约束自然自由发挥。第三类是随机种子没有固定同一组参数每次跑出来的结果都不同。排查顺序建议按“成本从低到高”来先检查风格锚点是否被正确拼接跑一遍build_shots.py看输出、再检查参考图是否存在且路径正确、最后才考虑调高参考图权重或固定种子重新生成。前两步几分钟就能确认第三步可能要重跑几十个镜头顺序搞反了会很浪费时间。5.2 音画不同步表现是画面比声音快或慢越到后面越明显。最常见的原因是帧率不一致。视频 24fps音频却按 25fps 的时间基准拼接累积误差会随时间线性放大。其次是采样率不统一48kHz 和 44.1kHz 混用某些播放器会做重采样补偿补偿不准就表现为漂移。还有一种情况是音频片段的时长被截断或补齐时没有对齐到帧边界。解决思路是强制统一视频全部转 24fps音频全部转 48kHz且每段音频的时长必须是 1/24 秒的整数倍。归一化脚本里的fps24和-ar 48000就是在做这件事。改完之后如果还有细微偏移可以在拼接后单独做一次音画对齐检查用逐帧对比的方式定位偏移点只对偏移的那一段做微调。5.3 拼接失败与编码报错concat报错信息通常很模糊只说某个文件参数不匹配。这时候不要猜直接用ffprobe把每个文件的实际参数打出来对比。# 批量检查每个片段的编码参数 for f in work/norm/*.mp4; do echo $f ffprobe -v error -select_streams v:0 \ -show_entries streamwidth,height,r_frame_rate,pix_fmt,codec_name \ -of csvp0 $f ffprobe -v error -select_streams a:0 \ -show_entries streamsample_rate,channels,codec_name \ -of csvp0 $f done输出里任何一行和其他不一致就是问题所在。把那个文件单独重新归一化即可不用全部重跑。如果某些片段干脆没有音频流静默镜头很常见concat -c copy也会失败因为流数量不一致。这种情况下有两个选择给静默片段补一条等长的静音音轨或者放弃-c copy改为重新编码。补静音音轨更省时间命令如下ffmpeg -y -f lavfi -i anullsrcchannel_layoutstereo:sample_rate48000 \ -i work/norm/s07.mp4 \ -shortest -c:v copy -c:a aac -b:a 192k \ work/norm/s07_fixed.mp45.4 排查速查表把上面几类问题整理成一张表出问题的时候按顺序过一遍能省不少时间。问题现象常见原因排查动作解决思路画面色调前后跳跃风格锚点未拼接检查 queue.json 里的 prompt统一由脚本拼接禁止手写风格词角色脸型变化明显参考图缺失或权重低确认 ref_image 路径存在补参考图并适当提高权重同一提示词结果不同种子未固定查看分镜表 seed 字段记录种子固定复现视频后半段画面崩坏单段时长过长检查生成时长设置拆成 5 秒左右的短段拼接报参数不匹配规格未统一用 ffprobe 逐段对比重新归一化异常片段拼接报流数量不一致部分片段无音轨检查 audio stream补等长静音音轨音画逐渐不同步帧率或采样率不统一对比 fps 与 sample_rate统一为 24fps / 48kHz字幕后段消失时间轴超出视频时长对比 SRT 末条与视频时长修正时间轴或延长片尾字幕字体不生效字体名写错查询系统已装字体使用准确的字体全名导出文件体积过大CRF 值偏低查看编码参数适当提高 CRF或改用两遍编码6. 工程化最佳实践6.1 命名、版本与可追溯性前面已经强调过命名规则这里补充“可追溯性”这个更上层的概念。所谓可追溯是指你拿到成片里的任意一个镜头能在三十秒内回答四个问题这个镜头是哪一版生成的、用了什么提示词、用了什么种子、参考图是哪张。做到这一点其实不难关键是每次生成都写日志。日志不需要复杂一行 JSON 就够{ts:2024-05-17T10:22:31,shot:s01,ver:v02,seed:20240517,model:xxx,dur:6,ref:archive_wide.png}有了这个日志后面无论是要重做某个镜头还是要统一微调某一批镜头的风格都有据可查。没有日志两周之后你自己都看不懂当时的文件是怎么来的。6.2 成本与算力控制AI 生成是要花钱的尤其是视频生成。控制成本有几个实用手段。第一先低分辨率试错。构图、光线、氛围这些大方向用低分辨率跑一遍就能看出来没必要一开始就上高分辨率。确认方向对了再高清重跑。这一条能省掉大量无效开销。第二抽帧预览代替整段生成。如果只是想确认运镜方向对不对可以先只生成前两秒看着不对就停不用等整段跑完。第三建立可复用的提示词库。同一类镜头比如“空镜过渡”“特写特写”的提示词是可以复用的攒下来之后新项目直接调用不用从零开始试。第四批量任务安排在非高峰时段。很多服务在高峰期排队久、失败率高失败重试本身就是隐性成本。6.3 合规、版权与安全边界这一节必须单独讲因为 AI 短片涉及的权利问题比想象中多。素材授权方面音效、配乐、字体、参考图每一样都要确认授权范围。尤其是字体很多免费字体只允许个人使用商用要另外授权。发布前务必逐个核实不要抱着“应该没事”的心态。生成内容的合规方面避免生成与真实人物高度相似的肖像避免使用他人商标、标识作为画面主体避免生成可能引起误解的虚构新闻画面。涉及真实机构、真实地点的内容宁可改成虚构名称。数据安全方面上传到在线服务的参考图可能包含个人信息或未公开的设计稿。上传前做一次脱敏判断涉及敏感内容的素材不要上传到第三方服务。项目文件如果多人协作用私有仓库不要公开。最后一点容易被忽略发布时要如实标注“本片由 AI 生成/辅助生成”。这不是自曝其短而是对观众的基本尊重也能避免后续的误解和争议。6.4 可维护性与协作如果你的项目不是一个人做那分镜表就是协作的核心契约。约定好谁能改哪个字段、改了要通知谁、status字段的状态流转规则是什么比什么工具都重要。推荐的状态流转是pending待生成→generated已出图→animated已出视频→approved已验收→done已入片。每个状态变更都伴随一次提交这样用 Git 的提交历史就能还原整个项目的推进过程。另外把“风格锚点”和“角色描述块”这类全局资产单独抽成文件由一个人统一维护。多人同时改全局风格一定会打架。约定一个 owner其他人只能提建议不能直接改这是成本最低的协作方式。7. 学习路线与动手建议如果这套流程你已经看明白了下一步建议按这个顺序动手不要一上来就做三分钟。第一阶段做 15 秒。只做三个镜头跑通“分镜表 → 静帧 → 视频 → 归一化 → 拼接 → 字幕”的完整链路。这个阶段的目标不是好看是让脚本全部跑通把环境问题和参数问题提前暴露。很多人卡在第一天就是因为想直接做完整片结果在拼接环节被一堆报错劝退。第二阶段做 60 秒。增加到十个镜头左右重点练一致性。同一个角色出现在三个以上镜头里风格锚点是否真的管住了画面这一步就能验证出来。第三阶段才是完整短片。这时候你已经有了可复用的脚本、验证过的提示词库、踩过的坑清单效率会比第一次高一个数量级。后续可以深入的方向有几个一是学习基础的镜头语言比如景别、轴线、视线匹配这些知识能显著提升分镜质量而且和用什么工具无关二是研究色彩管理理解 LUT 和色彩空间能让多来源素材的观感更统一三是把脚本进一步工程化比如加上失败重试、断点续跑、并发控制做成一个真正的小型流水线。最后提醒一句这类项目的风险点集中在三处——素材授权没核实、中间产物没备份、规格没提前统一。前两个是法律和数据层面的问题出了事很难挽回第三个是效率问题会让你在最后阶段反复返工。这三件事在项目启动的第一天就该定好规则而不是等到出片前才想起来。整套流程跑顺之后你会发现真正的门槛不在工具而在于你愿不愿意把一个创意拆成可以被机械执行的步骤。工具每个月都在变但“拆解、记录、验证、归一化”这套方法不会过时。
阅读完成 · 觉得有帮助?