这条流水线能做出什么
走完下面的步骤,你会得到一条 3~5 分钟的漫剧短片:画面是 AI 生成的漫画风格分镜,每个镜头有缓慢推拉或平移,人物有配音,屏幕下方有同步字幕,导出为 1920×1080、25 帧的 MP4,可以直接上传到视频平台。
整套流程不依赖某一家工具。文本、图像、语音、视频这四类能力,你用哪家服务都行,只要按下面的接口约定把中间产物落到本地文件夹即可。
先约定工程目录,后面所有命令都基于它:
```text
manju/
├── script/
│ ├── story.md # 故事梗概和台词
│ └── storyboard.yaml # 结构化分镜表
├── assets/
│ ├── characters/ # 角色参考图
│ └── shots/ # 分镜静帧 s01.png s02.png ...
├── audio/
│ ├── lines/ # 单句配音 s01.m4a ...
│ └── bgm.mp3
├── subs/
│ └── final.srt
├── build/ # 中间产物
└── out/
└── final.mp4
```
前置条件清单
- 一台能跑脚本的电脑,系统不限;显存不是硬门槛,出图交给云端服务也可以。
- Python 3.9 以上,会用
pip install装包。 - ffmpeg 与 ffprobe,命令行能直接调用(
ffmpeg -version有输出即可)。 - 四类账号各一个:文本生成、图像生成、语音合成、图生视频。接口地址和参数以各家官方文档当前版本为准。
- 一个剪辑软件作为兜底,用来手动微调字幕位置和补几个转场。
- 一份背景音乐,注意使用授权范围。
时间上,一条 3 分钟、约 30 个镜头的短片,写剧本和分镜占掉将近一半时间,出图和配音是等待为主,剪辑合成占两成。
第 1 步:把故事写成"可拍摄"的剧本
漫剧的剧本不需要文学性,需要的是每句话都能对应一个画面。用三列表格写:镜号、画面描述、台词。
先在 script/story.md 里写清楚三件事:
1. 一句话主线:谁,想做什么,被什么挡住,结果如何。
2. 角色表:每个角色的固定外貌特征,写到"换个人画也能认出是同一个"的程度。
3. 台词表:每句台词控制在 25 字以内,一句话一个镜头。
不要写"她心里很乱"这类无法画出来的描述,改成"她把手机扣在桌上,屏幕还亮着"。这是整条流水线里最省时间的一条经验。
第 2 步:把剧本转成分镜表
分镜表是后续所有自动化的数据源,用 YAML 写,人读得懂,脚本也好读。
```yaml
script/storyboard.yaml
style: "日系赛璐璐上色,干净线条,柔和自然光,画面中不出现任何文字"
characters:
- id: lin
name: 林小满
look: "17 岁女生,齐肩黑发,左侧别一枚蓝色发卡,米白色针织衫"
- id: chen
name: 陈经理
look: "40 岁男性,短寸头,深灰西装,细框眼镜"
shots:
- id: s01
scene: "傍晚的地铁站台,暖橙色顶灯,背景人群虚化"
cast: [lin]
action: "林小满低头看手机,眉头微皱,中景,略微俯视"
line: "又加班到这个时候。"
seconds: 4
- id: s02
scene: "空荡的办公室,只有一盏工位灯亮着"
cast: [lin, chen]
action: "陈经理把一叠文件放到桌上,林小满站在对面,过肩视角"
line: "这份明天早上要。"
seconds: 5
```
字段含义:scene 是环境,cast 引用角色 id,action 是动作和机位,line 是台词,seconds 是预计时长。style 是全局风格串,会拼到每个镜头里,保证画风统一。
第 3 步:锁定角色一致性
跨镜头人物变形是这类项目里返工最多的环节。做法是把角色外貌写成一个固定字符串,每次出图都原样拼进去,再配合一张角色参考图。
```python
build/prompt.py
import yaml
with open("script/storyboard.yaml", encoding="utf-8") as f:
sb = yaml.safe_load(f)
chars = {c["id"]: c for c in sb["characters"]}
def build_prompt(shot):
parts = [shot["scene"]]
for cid in shot.get("cast", []):
c = chars[cid]
parts.append(f'{c["name"]},{c["look"]}')
parts.append(shot["action"])
parts.append(sb["style"])
return ",".join(parts)
if __name__ == "__main__":
for s in sb["shots"]:
print(s["id"], "->", build_prompt(s))
```
跑一下 python build/prompt.py,检查每个提示词里角色描述是否完整。
更稳的做法是给主要角色单独训练一个小模型(LoRA),或者用参考图条件控制。是否需要、怎么用,以你所用工具的官方文档为准。固定随机种子只能提高复现率,不能保证跨模型一致。
第 4 步:批量出分镜图
写一个循环脚本:读分镜表,逐个镜头调用图像生成接口,把结果存成 assets/shots/s01.png 这样的命名。
```python
build/gen_shots.py
import time, pathlib, yaml
from prompt import build_prompt # 上一步的文件
sb = yaml.safe_load(open("script/storyboard.yaml", encoding="utf-8"))
outdir = pathlib.Path("assets/shots")
outdir.mkdir(parents=True, exist_ok=True)
def generate(prompt, ref_image=None, size="1920x1080"):
"""替换成你所用图像服务的调用。返回图片二进制内容。"""
raise NotImplementedError
for shot in sb["shots"]:
target = outdir / f'{shot["id"]}.png'
if target.exists():
continue # 断点续跑,别重复烧额度
prompt = build_prompt(shot)
try:
data = generate(prompt)
target.write_bytes(data)
print("ok", shot["id"])
except Exception as e:
print("fail", shot["id"], e)
time.sleep(2) # 给接口留点间隔
```
三个要点:
- 断点续跑。脚本要能跳过已经存在的文件,出到一半中断时不用从头来。
- 比例统一。所有镜头都用 16:9、1920×1080,避免后期画面忽大忽小。
- 失败重试。报错重试两三次,仍失败就记录下来,换成邻居镜头的构图重出。
出完图先人工过一遍,把人物崩坏、多手指、构图雷同的镜头挑出来重出。这一步偷懒,后面配音和剪辑会加倍还回来。
第 5 步:配音与时间轴
逐句配音,一句一个文件,文件名和镜号一致:audio/lines/s01.m4a。合成参数里语速放在正常偏慢的位置,句尾留一点静音,听起来才不像播报。
拿到音频后用 ffprobe 量出真实时长,不要用分镜表里预估的 seconds,因为人声实际长度常常差出一两秒。
```python
build/timeline.py
import subprocess, json, pathlib, yaml
def dur(p):
out = subprocess.run(
["ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "json", str(p)],
capture_output=True, text=True, check=True).stdout
return float(json.loads(out)["format"]["duration"])
def ts(t):
h, r = divmod(t, 3600)
m, s = divmod(r, 60)
return f"{int(h):02d}:{int(m):02d}:{s:06.3f}".replace(".", ",")
sb = yaml.safe_load(open("script/storyboard.yaml", encoding="utf-8"))
lead_in, gap = 0.6, 0.8 # 开头留白、句间留白,单位秒
lines, t = [], lead_in
for shot in sb["shots"]:
p = pathlib.Path("audio/lines") / f'{shot["id"]}.m4a'
if not p.exists():
print("缺少配音:", p)
continue
d = dur(p)
lines.append((t, t + d, shot["line"]))
t += d + gap
def srt_time(x):
h, r = divmod(x, 3600)
m, s = divmod(r, 60)
ms = int((s - int(s)) * 1000)
return f"{int(h):02d}:{int(m):02d}:{int(s):02d},{ms:03d}"
with open("subs/final.srt", "w", encoding="utf-8") as f:
for i, (a, b, text) in enumerate(lines, 1):
f.write(f"{i}\n{srt_time(a)} --> {srt_time(b)}\n{text}\n\n")
print("总时长约", round(t, 1), "秒")
```
这份 SRT 既是字幕,也是剪辑时间轴:每个镜头的画面长度就等于对应那句配音的时长加留白。
第 6 步:让静图动起来
两条路。图生视频服务直接产出几秒的短片段,效果自然,成本和时间更高;本地用 ffmpeg 做推拉平移,速度很快,适合对白密集的镜头。
推拉平移的命令(5 秒、25 帧):
```bash
ffmpeg -y -loop 1 -i assets/shots/s01.png \
-vf "scale=2400:-2,zoompan=z='min(zoom+0.0008,1.15)':d=125:s=1920x1080:fps=25,format=yuv420p" \
-t 5 -c:v libx264 -pix_fmt yuv420p build/s01.mp4
```
先放大到 2400 宽再裁到 1920×1080,是为了让平移过程中不会露出黑边。d=125 是帧数,改成 每秒帧数 × 秒数 即可。左右平移把 zoompan 换成 x 方向的表达式即可实现。
人物说台词的镜头,用极缓慢的推进;环境镜头用平移。全片不要出现方向、速度都一样的一串镜头,否则看着像幻灯片。
第 7 步:合成成片
先把所有镜头片段按顺序拼起来:
```bash
cd build
printf "file '%s'\n" s*.mp4 > list.txt
ffmpeg -y -f concat -safe 0 -i list.txt -c copy rough.mp4
```
再做音轨:把每句配音按时间轴摆进去。简单做法是生成对应长度的静音,和配音交替拼接:
```bash
ffmpeg -y -f lavfi -i anullsrc=r=44100:cl=mono -t 0.6 -c:a aac build/silence.m4a
```
按"静音 → s01 → 静音 → s02 → …"的顺序写进音频列表,用 concat 拼接得到 voice.m4a,然后和背景音乐混音:
```bash
ffmpeg -y -i build/voice.m4a -i audio/bgm.mp3 \
-filter_complex "[1:a]volume=0.15,aloop=loop=-1:size=2e9[bg];[0:a][bg]amix=inputs=2:duration=first:dropout_transition=0[a]" \
-map "[a]" -c:a aac build/mix.m4a
```
背景音乐音量压到人声的两三成,漫剧的对白密度高,音乐抢了就没法听。
接着把字幕烧进画面,并合上音轨:
```bash
ffmpeg -y -i build/rough.mp4 -i build/mix.m4a \
-vf "subtitles=subs/final.srt:force_style='FontSize=22,Outline=2,MarginV=40'" \
-map 0:v -map 1:a -c:v libx264 -crf 20 -preset medium \
-c:a aac -b:a 192k -shortest out/final.mp4
```
如果字幕显示成方块,是字体缺失,在 subtitles 过滤器里指定中文字体文件和字体名即可。
第 8 步:验收清单
导出后完整看两遍。第一遍关掉声音看画面,检查镜头切换和字幕;第二遍只听声音,检查有没有爆音、断句、突然变响。重点核对:
- 人物在所有镜头里是否长得像同一个人。
- 字幕有没有超出画框、有没有和人物脸部重叠。
- 音画是否对得上,尤其是有明显动作的镜头。
- 总时长和平台要求是否匹配,超出就删镜头而不是加速。
常见坑与排错
人物每个镜头都不一样。 多半是角色描述串在不同镜头里被改写了。把描述放进 YAML 只写一次,脚本统一拼接,不要手打。
配音和画面对不上。 用预估时长排时间轴导致的。坚持用 ffprobe 里实测的时长,SRT 和画面长度都从同一份时间轴生成。
画面抖动或边缘发黑。 推拉幅度过大,或原图没有留出裁剪余量。先把图放大到目标宽度的 1.2 倍以上再动,幅度控制在 10% 以内。
音轨拼接处有咔哒声。 采样率不一致。把所有音频统一转成 44100Hz、单声道或立体声同一种,再拼。
接口报错、任务排队很久。 图生视频类任务通常是异步的,提交后拿到任务号再轮询查结果,别用同步请求硬等。并发数、轮询间隔、配额规则以各家官方文档当前版本为准。
每段重跑都要重烧钱。 全部中间产物落盘,脚本做存在性判断。改一句台词只重跑那一个镜头。
画面里出现乱码文字。 生成时在提示词里明确"画面中不出现任何文字",把字留给字幕层。
下一步建议
跑通第一条之后,按这个顺序加深:
1. 把分镜表加上 transition 字段,脚本里自动套用不同转场,减少手工剪辑。
2. 积累角色参考图和提示词模板,形成自己的素材库,新项目直接复用。
3. 把整条链路包成一个命令,比如 python run.py --episode 03,从分镜表一路跑到成片。
4. 多集连载时,把角色表、风格串抽成公共配置文件,各集只写 shots。
做到第 3 步,一集 3 分钟的漫剧,从写好分镜表到出片可以压缩到半天以内。
