视频转漫画流水线(Video to Comic)
接收一组视频,输出可直接发布的漫画页(或可继续加工的半成品)。
与 photo-to-comic-strip 的关系:那个技能处理「已有照片」,本技能处理「原始视频」并多做前三个阶段的视频理解。画风库与角色锚点方法两边共享,改风格时两边都要改。
何时用 / 不用
| 情况 | 用什么 |
|---|---|
| 一段或多段视频,要自动出漫画 | ✅ 本技能 |
| 手上已经是挑好的照片 | photo-to-comic-strip |
| 只是想把一张照片插画化 | 直接调用图像生成,别套流程 |
技能构成
| 文件 | 作用 |
|---|---|
scripts/extract_keyframes.py |
阶段一:视频解析 + 抽帧 + 去重 + 联系表 |
scripts/shoot.py |
阶段六:注入配置 + 截图 + 自动裁边 |
assets/comic-template.html |
排版模板(4 种形态 × 2 套主题) |
references/prompt-library.md |
提示词库(画风段 / 锚点段 / 场景段 / 旁白指令) |
两个脚本都只依赖 ffmpeg + Pillow,不需要 ffprobe / numpy / cv2。
启动前必做:告知成本
图像生成每张消耗约 5–10 积分,属额外计费。开工前必须向用户报清张数与总量,得到确认再跑。
| 形态 | 张数 | 估算消耗 |
|---|---|---|
| 四格 2×2 | 1 锚点 + 4 格 = 5 | ~25–50 |
| 六格 2×3 | 1 + 6 = 7 | ~35–70 |
| 九格 3×3 | 1 + 9 = 10 | ~50–100 |
| 条漫 5 格 | 1 + 5 = 6 | ~30–60 |
若不想消耗积分:只跑阶段一到三,交付分镜脚本 + 排版占位版 HTML(版式已排好、格子留空),用户后续自己补图。这同样是完整交付物。
工作目录约定
所有产物落在一个工作目录下,阶段间用文件传递,任何一步都能中断续跑:
<workdir>/
├── frames/ # 阶段一:关键帧
├── sheets/ # 阶段一:联系表(给模型看的)
├── frames.json # 阶段一:清单
├── analysis.json # 阶段二:视频理解结果
├── storyboard.json # 阶段三:分镜脚本
├── anchor/
│ └── character-anchor.png # 阶段四:角色锚点(跨批次长期复用)
├── panels/ # 阶段五:逐格漫画图
├── storyboard.json # 阶段三:分镜脚本(shoot.py 的输入)
├── comic.html # 阶段六:排版文件
└── comic.png # 阶段六:成品图
阶段一 · 视频解析与关键帧提取
python {baseDir}/scripts/extract_keyframes.py \
--input "<视频文件或包含视频的目录>" \
--outdir "<workdir>" \
--interval 8 --scene-threshold 0.30 --max-frames 48
参数怎么调:
| 参数 | 默认 | 什么时候改 |
|---|---|---|
--interval |
8 | 长视频(>10 分钟)调到 15–20;短视频(<1 分钟)调到 2–3 |
--scene-threshold |
0.30 | 抽到的帧太少调到 0.20;废帧太多调到 0.45 |
--max-frames |
48 | 上限,控制后续读图成本。四格漫画其实 24–32 就够 |
--sheet-cols |
5 | 联系表列数 |
脚本不依赖 ffprobe、numpy、cv2,只用 ffmpeg + Pillow。传目录时会按文件名自然序拼接,时间轴跨文件连续。
已内置的健壮性处理(不用你操心,但知道有这回事):
| 情况 | 脚本的处理 |
|---|---|
片段比 --interval 还短 |
有效间隔自动压到片段时长的一半,保证每段至少贡献两帧。否则 15 秒片段配 --interval 20 会一帧都抽不到 |
ffmpeg 新版移除了 -vsync |
运行时探测参数名,新版用 -fps_mode passthrough,老版回退 -vsync 0。不加这个参数的话,被选中的 2 帧会被重复帧填满成 32 个文件,时间戳全错 |
| 素材画面高度重复 | 去重按「结构像 且 颜色也像」双条件判定;丢掉九成以上候选帧时告警并提示调参 |
| 片段数量多(几十上百个) | 不逐条刷屏,末尾给汇总,明细写进 frames.json |
| 中间文件残留 | 每次运行前清理旧的 frames/ 目录 |
产出:frames/(关键帧)、sheets/(联系表)、frames.json(含每帧时间戳、来源、类型、感知哈希)。
阶段二 · 内容分析(读图理解)
关键成本控制:先读 sheets/ 里的联系表(一张图含 20 帧),一次通读全片;再挑少数几张读 frames/ 原图做细节分析。
> ⚠️ 不要一上来就逐张读原图。48 帧原图会白白烧掉大量上下文,而联系表已经能覆盖 90% 的判断。
读联系表时,要求模型输出这份结构化结果,落到 analysis.json:
{
"main_line": "一句话概括这条视频发生了什么",
"setting": "地点、天气、时段",
"characters": [
{
"role": "主角",
"appearance": "性别/年龄段/发型/体型/标志性特征",
"wardrobe": "全程穿着的服装(含每段变化)",
"frames": ["v01_003", "v01_007"]
}
],
"beats": [
{ "frame": "v01_003", "t": 12.4, "what": "发生了什么事", "why": "为什么值得画" }
],
"visual_style": "素材本身的影像风格(色调/光线/镜头语言)",
"suggested_genre": "基于素材调性推荐的漫画画风",
"usable_frames": ["v01_003", "v01_007"],
"rejected_frames": [{ "frame": "v01_011", "reason": "纯风景空镜/重复/糊" }]
}
characters[].appearance 与 wardrobe 写得越细,阶段四的锚点越准。
阶段三 · 分镜脚本
用 references/prompt-library.md 里的「事件提炼」与「旁白生成」指令,把 analysis.json 压成 storyboard.json:
{
"title": "漫画标题(≤10 字,有画面感)",
"date": "2026-09-13",
"layout": "grid-4",
"theme": "paper",
"style_block": "(逐字固定的画风段,整批共用)",
"panels": [
{
"no": 1,
"source_frame": "v01_003",
"shot": "wide establishing shot",
"scene": "英文场景描述:地点 + 动作 + 景别 + 光线 + 此刻服装",
"caption": "中文旁白(≤12 字)",
"sfx": "拟声词,可省"
}
]
}
选帧判据:读者看这几格能不能明白「这天发生了什么」。
纯风景空镜、重复画面、无信息量的过渡帧一律不选。
景别要有变化——四格里至少一格远景、一格特写。全是中景会很闷。
阶段四 · 角色锚点(一致性地基)
从 usable_frames 里挑人物最清晰的那一帧,用图像生成(image-to-image)转成漫画版人物。
prompt 用 references/prompt-library.md 的「角色锚点段」(画风段 + 角色描述 + 定妆照要求)。
产出存为 anchor/character-anchor.png,全片所有格子引用同一张。
纪律:
- 锚点里服装定死成常穿那套
- 换画风 = 重做锚点(绘本风锚点跑水墨风分镜,人物会精神分裂)
- 锚点做好后给用户看一眼再往下跑——这一步不对,后面全废
阶段五 · 逐格生成
每格一次图像生成调用,输入两张图:
image1= 该格对应的原始关键帧(定构图与场景)image2= 角色锚点(定人脸与服装)
prompt 结构:
[画风段,逐字固定] + [本格英文场景描述] + [角色一致性约束]
参数建议:
| 参数 | 值 | 理由 |
|---|---|---|
input_fidelity |
中–高 | 越高越贴近原图;太高中和掉画风,建议先试中间值 |
size |
与排版格子一致 | 四格用 1024x1024 或 1536x1024,条漫用 1536x1024 |
quality |
medium |
先出草稿打版式,确认后再用 high 重跑 |
先跑 1 格试水,确认画风与人物一致性到位,再批量跑剩下的。别一口气跑完九格才发现风格不对。
常见问题与处置
| 症状 | 原因 | 处置 |
|---|---|---|
| 每格脸不一样 | 漏传锚点,或锚点没定死 | 检查 image2 是否传了;确认锚点只有一张 |
| 场景串味(头盔没摘) | 场景描述里没写「此刻服装」 | 每格显式写 now wearing ... |
| 四格画风不统一 | 风格段被改动了 | 风格段逐字固定,只换场景段 |
| 画面出现乱码汉字 | 模型自己画了字 | prompt 加 no text, no lettering;已出的图用图像编辑模式擦除 |
| 人物姿态僵硬 | 只给了远景锚点 | 锚点用半身/全身清晰图,场景里写清动作 |
阶段六 · 排版输出
复制 assets/comic-template.html 到工作目录,改内嵌的 COMIC_CONFIG:
const COMIC_CONFIG = {
layout: "grid-4", // grid-4 | grid-6 | grid-9 | strip
theme: "paper", // paper(绘本·水墨)| dark(电竞·霓虹)
title: "周末骑行日记",
date: "2026-09-13",
footer: "山与我 · Mountain and Me",
frames: [
{ src: "panels/panel-01.png", caption: "六点半就爬起来了" },
...
]
};
模板特性:
- 四种形态靠
layout切换,两套主题靠theme切换 - 缺图的格子自动显示「待生成 XX」占位——分镜没出图也能先看版式
- 支持
sfx字段渲染拟声词 - 加
?shot=1去掉页面外边距,截图更干净 - 支持
?config=xxx.json从外部喂配置(需经 http 服务,file://下会被 CORS 拦)
截图成图:用配套脚本一步到位。
python {baseDir}/scripts/shoot.py \
--html {baseDir}/assets/comic-template.html \
--config "<workdir>/storyboard.json" \
--out "<workdir>/comic.png" \
--width 1080 --height 1800
脚本做了三件省事的事:
- 自动注入配置 —— 把
storyboard.json内联进模板再截图,无需起 http 服务,绕开file://下 fetch 被 CORS 拦的问题。frames[].src的相对路径按 JSON 文件所在目录解析。 - 自动定位浏览器 —— 优先用
chrome-headless-shell。⚠️ 本机实测:完整版 Chrome 加--headless=new会挂住不返回,必须走 headless-shell。 - 自动裁掉底部空白 —— 无头截图只能按窗口尺寸出图,窗口给高才不截断内容,代价是底部拖一片空白。脚本用逐行平均色找内容边界自动裁掉(背景是渐变,没法用比对单色的办法)。
--height 给足就行,裁切会自动处理。条漫(strip)形态内容很高,建议给 3600。
截图备选方案:browser-skill (bsk) 驱动真实 Chrome,适合模板里用了 ?config= 外部加载、或需要交互调整的场合。
降级方案
| 断点 | 降级做法 |
|---|---|
| 视频无法解析(编码异常/损坏) | 让用户用系统播放器导出截图,从阶段二开始跑 |
| 图像生成不可用或不想消耗积分 | 只交付阶段一到三 + 排版占位版 HTML,用户自行补图 |
| 找不到人物清晰的帧 | 用场景帧生成「无主角」的风景漫画,或让用户补一张人像照片做锚点 |
| 联系表太大读不清 | 调小 --sheet-cols 或 --cell-width,拆成多张联系表 |
质量检查清单
交付前逐条过:
- [ ] 四格连起来能讲清一件事(不是四张精美插画)
- [ ] 主角四格都是同一张脸、同一套衣服(换装处有明确交代)
- [ ] 景别有变化(至少有远景 + 特写)
- [ ] 光线/时段连贯(不会一格清晨一格正午)
- [ ] 每格旁白 ≤12 字,且不鸡汤
- [ ] 画面里没有模型画出来的乱码文字
- [ ] 手机竖屏看,格子里的字能看清
踩坑记录
- 每格必须写「此刻的服装」 —— 实测:只写「晚上在家看手机」,模型会让主角戴着骑行头盔坐沙发上。
- 风格段逐字固定 —— 改一个词,四格就像四个人画的。
- 不要依赖模型画字 —— 一律由排版层叠加。
- 联系表优先于原图 —— 先读联系表通读全片,再挑帧读原图,否则上下文会被撑爆。
- 先跑一格试水 —— 画风没锁定就批量跑,是纯粹的积分浪费。
- 锚点是长期资产 —— 同一个主角的锚点存下来,跨批次复用,这才是「日更漫画」能成立的前提。
- 完整版 Chrome 无头模式会挂住 —— 本机实测
--headless=new跑到超时都不返回;截图一律走chrome-headless-shell(shoot.py已内置这个优先级)。 ffprobe不一定装了 —— 所以元信息从ffmpeg -i的 stderr 里解析,只有 ffmpeg 也能跑。?shot=1参数别丢 —— 它去掉页面外边距,截图才干净。-vsync在新版 ffmpeg 已被移除 —— 传了会Unrecognized option直接报错退出、一个文件都不出。这个报错极具迷惑性:抽帧结果为空,很容易被误读成「这段视频没有场景切换」。pick_sync_flags()会自动探测,改脚本时别再手写死-vsync。- 时间戳不要用公式推算 —— 一律从
showinfo的pts_time取实际值。用i*interval推算的话,遇到比 interval 短的片段,整条时间线就错位了。 - 去重必须同时看结构和颜色 —— dHash 要先转灰度,色相信息全丢。只用它的话「同构图不同调色」的镜头会被判成同一帧(实测 24 帧去重后只剩 1 帧)。现在的签名是「dHash + 4×4 彩色缩略图」,两个维度都像才算重复。
- 颜色指纹别用「平均色」 —— 色彩分布均匀的画面(比如大片天空、彩条)平均下来会互相抵消成中性灰,色相怎么转都一样,等于没判断。要用缩小后的彩色图。