product-motion-gif
产品图/场景图 → 可循环动图。核心是 GPT-Image 2.5 的一致性:一次生成 4x4=16 帧多格图,
再切帧合成。不要一帧一次生成——那样漂移严重且贵。
什么时候用
- 用户给产品图或场景图,要"动起来"的展示素材(环绕、旋转、悬浮)。
- 用户不给参考图,只说"生成一个 XX 在做 XX 的动图"(角色招式 / 游戏人物动作)——
apiz_image.py的--ref是可选的,纯文本生成也支持(见下"四类动效")。 - 用户给人像/人脸图,要做"表情包"(同一个人做一整套夸张表情,如"周星驰那种夸张")
—— 用 D 类模板prompts/face-expression-loop-4x4.txt,参见"四类动效"。 - 输出目标:微信/详情页可用的 GIF、现代平台用的 WebP、投放用的 MP4。
不适合:多主体互动、复杂剧情动作、刁钻运镜、需要 48 帧以上的长动画。
四类动效,别抄错对方的写法
| A. 相机绕场景(orbit 系列) | B. 相机锁死、角色做动作(char-action-fixed-4x4.txt) |
C. 相机锁死、背景后掠、角色做动作(char-action-scroll,待固化) |
D. 机位锁死、只变表情(face-expression-loop-4x4.txt) |
|
|---|---|---|---|---|
| 参考图 | 必须给 | 不用给,从零画 | 不用给,从零画 | 必须给——脸就是身份 |
| 相机 | 绕主体扫 60° | 完全不动(no orbit / zoom / dolly / pan) | 完全不动 | 完全不动,且锁得最死 |
| 背景 | 必须横移——转动的唯一线索(硬规则 17) | 纹丝不动 | 必须向后掠——速度感的唯一来源(近快远慢视差) | 纹丝不动(纯黑底最稳) |
| 主体 | 居中不动,转动由背景承载 | 居中,动作全在它身上 | 居中,动作在它身上,它不横穿画面 | 一张脸(头肩),居中等大,只有面部肌肉在变 |
| 循环 | --loop pingpong(硬规则 3) |
--loop cycle,cell 16 收回起手式闭环 |
--loop cycle,整圈动作(如空翻 360°)首尾同姿 |
--loop cycle,cell 16 收回 cell 1 的中性脸 |
| 适用 | 产品/场景展示 | 招式、技能、站立动作 | 飞行、奔跑、冲刺这类有速度的动作 | 表情包、面部情绪演出 |
A 的"背景必须横移"照抄进 B 会把平涂底搅花;B 的"背景纹丝不动"照抄进 C 就没有速度感;
C 的"背景后掠"照抄进 A 会让环绕读成推轨;把 B/C 的全身动作写法抄进 D 会让脸在画面里乱跑。
D 类三条专属写法(照抄自模板,别删):
- 机位锁得比 B/C 更死。 脸对平移的敏感度远高于全身——全身横移 1% 看不出来,
脸横移 1% 就已经像在点头晃脑。所以模板里那句 "nothing in the picture changes except the
expression on it" 是核心约束。 - 允许点名"节拍",但接缝要写成过渡。 硬规则 5 禁止分段描述是针对连续量(角度、位移)
的;表情是离散状态(中性 / 坏笑 / 震惊 / 大笑),本来就该分节拍。写法:节拍只落在
具体格号的 ANCHORS 上(cell 1 / 5 / 9 / 12 / 16),并加一句
"the face never snaps instantly from one extreme to another"。 - 所有不对称特征必须显式给方位,且统一用观众视角。 只写 "a crooked smile" 不写左右,
模型会逐格随机镜像,观感就是脸在抖。用the corner of the mouth on the viewer's left,
不要用his left(模型常算反)。
D 类首个实例:m1.jpg 低多边形人像做夸张表情包(中性→挑眉坏笑→瞪眼震惊→咧嘴大笑→回中性),
2K / 3:4(单格 384×512)/ Flare,prompt 5305 字符 → D:/image25/gif-lab/out/face-expr/。
尚未经用户验收,暂不并入硬规则。
C 类提示语要点:把"角速度"写成可数的整数步(如 15 equal steps of 24 degrees = 整圈 360°),
并明写"背景每格后掠相同的量、近处比远处走得远";同时保留 B 类的身份锁与人口线。
C 类首个实例:孙行者翻筋斗云飞速前进(任天堂 3D 卡通渲染 + 蓝天绿丘后掠 + 完整前空翻),
prompt 5975 字符 → D:/image25/gif-lab/out/monkeyking/。尚未经用户验收。
稳定性最好的区间是「纯色 / 影棚背景 + 单主体 + 简单动作」。平涂底的全出血插画
(整幅就是纯色底、没有道具,如 k1)实测主体一致性同样很好、可以放心用——难点不在画得像不像,
而在转动读不读得出来:背景没东西可横移时只能靠主体剪影承载,必须按硬规则 17 把
"该变的部件"逐个写清。真正的雷区是背景杂乱 + 多主体:越界会随机抖动、画面漂移,
甚至出现生物/物理准确性错误。
前置条件
apizCLI 已安装并登录(apiz auth status)。模型见下"模型选择"。- Python 需
Pillow、numpy;MP4 需要ffmpeg。 - Windows 上若
bash报head/ls: command not found,先补 PATH:
export PATH="/c/Windows/System32:/c/Windows:/c/Users/49707/.workbuddy/binaries/PortableGit/versions/1.2.0/usr/bin:$PATH"
模型选择(省钱技巧)
| 模型 | 用途 | 实测成本(high/2K) |
|---|---|---|
apiz/gpt-image-2.5-flare |
试 prompt 阶段默认用这个 | 16:9 → 39 points;4:3 → 53 points |
apiz/gpt-image-2.5-sunburst |
定稿出正式版 | 约为 Flare 的 2 倍(16:9 → 78 points) |
两者参数完全相同,改 --model 即可切换。试错一律走 Flare。
尺寸档位:默认 2K;用户要"更清晰/4K"时才上 4K
默认一律 --size 2K。 用户明确说"不清晰""来个 4K"时才用 --size 4K
(那是授权花钱,否则算擅自重跑——见「验收」一节)。
要分清两件事,别只记住"4K 是假的":
一、4K 不是原生 4K 生成。 实测(Flare / high / 4:3 / 4K)元数据:
"upstream_width": 2048, "upstream_height": 1536 ← 上游其实只生成 2K
"target_width": 3264, "target_height": 2448 ← 放大 1.59 倍交差
"scale": 1.5938
"price": 133 ← 2K 是 53
二、在"固定输出尺寸"下比锐度,4K 白花钱。 同一素材、同样输出 640×480:
| 素材来源 | 拉普拉斯锐度 | 高频能量占比 |
|---|---|---|
| 2K → 640 | 22.35 | 51.4% |
| 4K → 640 | 23.08(+3.3%) | 52.9% |
差异在噪声量级,肉眼不可辨。
三、但 4K 真正的价值是"可用的原生输出上限"——这才是它该用的场合:
| 生成尺寸 | 单格像素 | 能原生输出的长边 |
|---|---|---|
| 2K | 384×512(4:3) | 512,拉到 640 已经在放大 1.25 倍,发软 |
| 4K | 612×816(4:3) | 816,零放大 |
→ 成品长边 ≤ 512px:用 2K,省 2.5 倍钱。
→ 成品长边要做到 800px 左右:2K 在物理上供给不了像素,必须用 4K
(j1 的 816×594 原生版就是这么来的;i1 用户说"不清晰"的根因也是这个,
不是合成参数问题)。
→ 想再大只能改网格(3×3 → 2K 下单格 682px),代价是帧数变少、流畅度下降。
→ 跨版本比清晰度用 scripts/compare_clear.py(同格归一化到同一显示尺寸 + 2x 局部放大),
别凭感觉说"更清晰了"。
透明底 + 像素风:五条链路硬约束(已实测)
这一节的每一条都是踩出来的,不是猜的。
一、透明底走 --transparency,返回的是 PNG。
apiz_image.py --transparency → params.transparency = true,实测返回 content_type: image/png,
带 alpha 通道。make_gif.py --bg transparent 配合使用(--bg auto 也会自动识别出 alpha)。
二、模型给的 alpha 是"柔边"的,而 GIF 只有 1-bit 透明。
实测一张透明底像素图:alpha 有 255 种取值,alpha == 0 占 50.7%,
其余从 1 连续过渡到 254,没有任何一个像素是 255。
giflab.quantize_frames 用 > 128 阈值二值化,后果是:
- 轮廓上 1–2px 的羽化被切掉或补上 → 边缘变硬(像素画正合要求,无害);
- "半透明残影 / 柔光拖尾"会被阈值化成实心色块,观感是脏污而不是残影。
→ 要干净的残影/拖尾,必须在 prompt 里要求"用实心像素画残影,不许半透明"。
→ WebP 支持真 alpha,是透明素材的正确载体,必须同时交付 WebP。
三、像素风必须 --resample nearest --no-dither。
默认 LANCZOS 会把硬边糊掉;Floyd 抖动会在平色区撒噪点,并在透明轮廓上造出成片半透明像素。
两个开关都是为像素风加的(resize_all(resample=...) / quantize_frames(dither=...))。
四、--size 只能取"源单格长边"或它的整数分之一,否则像素块大小不均匀。
resize_all 按"长边 = --size"等比缩放。用 NEAREST 做非整数倍缩放 = 像素块一大一小
(1px、2px、1px、2px…),这是像素画最显眼的硬伤。
→ 2K 4:3 单格是 512×384,所以 --size 用 512(不缩放) 或 128(精确 1/4)。
→ 坑:--trim union 会改变帧尺寸(实测 512×384 裁成 460×384),
此时按 512 输出等于 NEAREST 放大 1.11 倍,正好踩中上一条。
像素风请用 --trim none(顺带白送一个好处:16 格画框完全一致,对游戏素材是加分项),
或先看日志 global crop ... -> WxH 再定 --size。
五、透明底 + WebP(method=6) 很慢,别用前台跑;失败先查真实原因。
- 透明 512px × 16 帧导出 WebP 实测 > 3 分钟,前台默认 120s 超时会把它 SIGTERM 掉,
且输出走管道是块缓冲的,日志全丢——现象是"exit 1 / SIGTERM / 无任何输出"。
→ 长任务一律 run_in_background;python -u 关缓冲看实时日志。
- 透明素材跳过 MP4(--no-mp4):export_mp4 把帧合成到白底,透明信息本就不存在。
- apiz 生成失败时别猜:apiz tasks get <task-id> 能拿到 result.error。
实测一次失败的真实原因是
Your request did not meet content safety guidelines. Your tokens have been credited back
——疑似被 run-and-gun(含 "gun")触发,改成 action platformer 后同一份 prompt 一次通过。
内容安全被拒不扣 points(已退回),可以直接改词重试。
标准流程
SK="D:/image25/.workbuddy/skills/product-motion-gif"
PY="C:/Users/49707/.workbuddy/binaries/python/versions/3.13.12/python.exe"
# 1. 参考图入库,拿公网 URL(apiz 只吃 URL,不吃本地路径)
apiz upload ./product.jpg --folder myproj
# -> https://cdn-hk.51sux.com/storage/myproj/....jpg
# 外链图可直接转存:apiz transfer "<url>"
# 2. 生成 4x4 多格图(prompt 用 prompts/ 下的模板,按主体改前两段)
# 默认模板 prompts/orbit60-l2r-4x4.txt = 左 30° → 右 30°(见硬规则 1)
# 画幅跟随原图:方形→1:1,4:3→4:3,16:9→16:9,竖构图(如 1024×1598)→2:3
# 默认 --size 2K;只有用户要求"更清晰/4K"时才改 4K(见「尺寸档位」)
# ⚠ 整份 prompt 必须 ≤6000 字符:实测 6449 可用、9213 被接口拒收
# (HTTP 400 "prompt 必填且长度为 1-4000 字符")。
# 注释/变更记录写进同名 .notes.md,不要混在 prompt 文件里;
# apiz_image.py 会自动截掉单独成行 "---" 之后的全部内容(TO OPERATOR / CHANGE LOG)。
# 它会打印 "[apiz_image] prompt chars: N",超限直接本地报错,不浪费请求。
"$PY" "$SK/scripts/apiz_image.py" \
--ref "<上一步的 URL>" \
--prompt-file "$SK/prompts/orbit60-l2r-4x4.txt" \
--out out/sheet.json --model apiz/gpt-image-2.5-flare \
--quality high --size 2K --ratio 4:3
# 3. 切帧编号 → 立刻给人看多格图(不能省、不能拖,也别忘了给人"怎么看")
"$PY" "$SK/scripts/contact_sheet.py" out/sheet.jpg out/contact.png 4 4 480
"$PY" "$SK/scripts/ab_frames.py" out/sheet.jpg out/ab-01-16.png 1 16 4 4 620
"$PY" "$SK/scripts/motion_smooth.py" out/sheet.jpg 4 4 # 量化数字,仅作参考
# 4. 切帧 + 合成
"$PY" "$SK/scripts/make_gif.py" --sheet out/sheet.jpg --outdir out/run1 \
--rows 4 --cols 4 --canvas tight --bg auto --loop pingpong \
--keep-frames --quality-report out/run1/qa.json
产物:animation.gif、animation.webp、animation.mp4,加 frames/ 和 qa.json。
(--size 640 --timing ramp --duration 70 --ramp 2.6,0.7,1.8 --max-kb 3000 都已是默认值,
不用重复写。)
多个动图 → 一部短片(make_reel.py)
用户说"把刚才做的动画做成一个视频 / 合集 / 作品集"时走这条路,不要手搓 ffmpeg concat。
"$PY" "$SK/scripts/make_reel.py" --spec reel.json --out out/reel/reel.mp4
reel.json 只写「分镜表」,其余全有默认值:
| 键 | 作用 |
|---|---|
width / height / fps |
画布(默认 1280×720 / 30fps) |
round_ms / loops |
每轮动画铺多少毫秒 / 每段播几轮(默认 1500ms × 2 ≈ 3 秒一段) |
root |
段落里 file 的相对根目录 |
cover |
封面卡:title / subtitle / note / seconds |
ends |
结尾卡:title / lines(每项 ["标签","说明"])/ seconds |
segments[] |
每段:file / tag(家族标签)/ title / note;可选 pixel、scale_cap、bg |
四条已经踩过的实现细节(改脚本时别改回去):
- 非像素帧"只缩不放" —— 把小图一律放大到统一尺寸会让所有素材一起发糊;
宁可各段显示大小不一,也不要集体降质。 - 像素帧走 NEAREST 整数倍(
"pixel": true,默认上限 4 倍)。所以同一内容的
512 原生版和 128 颗粒版能做到显示尺寸完全相同(128×96 ×4 = 512×384),
观众看到的差异纯粹是颗粒度,不会被尺寸差干扰。 - 透明底 GIF 直接铺
bg(默认画布底色),不要先合成到白底再进来。 - 逐帧写进 ffmpeg 的 stdin(rawvideo),不落地中间 PNG —— 54 秒 720p 约 30 秒跑完,
磁盘也不涨。中文标题用C:/Windows/Fonts/msyhbd.ttc+msyh.ttc,无需额外字体文件。
选段原则:同一素材的不同参数变体(例如跑鞋的 v1/v2/v2p/v2o/v2f/v2h/s1/s2 共 8 版、
乐高城门 3 版)在片子里只放一个,否则观众看到的是"同一画面反复出现"。
要配旁白时:make_voiceover.py(edge-tts)
"$PY" "$SK/scripts/make_voiceover.py" --spec reel.json --out reel-vo.mp4 \
--voice zh-CN-YunxiNeural --rate "+6%" --pad 0.7 --min-sec 3.1 --gain 4
在同一个 reel.json 里给封面 / 每段 / 结尾各写一句 "say",其余全自动:
- 逐句 edge-tts 合成,缓存在
<out>.vo/,重跑不重复烧网络; - 每段时长 = max(min_sec, 语音时长 + pad),封面/结尾另给 pad;
- 渲染无音轨 mp4 并输出
timeline.json,再按时间轴把每句语音 adelay 到段落起点,
与静音底 amix 成一条音轨,最后 mux。
顺序不能反:必须是「先合语音量长度 → 再按语音长度定段落时长 → 最后渲染并混音」。
反过来(先按固定时长渲染再去配音)会把语音切掉,或每段留一大截空白。
踩过的两个坑(已修):
- 旁白整体偏后整整一段:make_reel.py 的 _mark() 曾把 enc.n / fps 当段首记,
实际那是段末时间,于是每句都被推后一段(封面那句跑到 5 秒才响)。已改成
(self.enc.n - nf) / fps。核对办法:timeline.json 里每段 start 必须等于
上一段的 end;再用 silencedetect 看开头有没有一大段静音。
- edge-tts 输出偏轻:峰值只到 -5dB(mean 约 -25dB),直接用会显得小声,
默认 --gain 4 抬到峰值 -1.2dB。中文语速实测约 3.4 字/秒(--rate +6% 后约 3.6),
解说词按这个估长度,10–14 字一句刚好卡在 3–4 秒的段落里。
硬规则
前两条是已验收的效果标准,改动前先问用户。
- 展示类环绕默认「左 → 右 60°」,不要 180°、也不要用 90°。(已验收)
准确指:cell 1 = 参考视角左 30°、cell 8 = 正好是参考视角、cell 16 = 参考视角右 30°,
相机从左往右扫过 60°,参考视角落在整张表的正中间。
模板:prompts/orbit60-l2r-4x4.txt(默认)。
验收历程:180° 太大(依次看到正面→侧面→背面,那是"转圈圈"不是"产品展示")→
90° 仍偏大 → 60° 通过 → 方向从"单向 0°→60°"改成"左 30°→右 30°"后转角明显更好读
(j1 实测花树/瀑布/木船相对峰体大幅换位,比此前各版都明显),故定为默认。
prompt 里必须写死这一句,否则模型会把 cell 1 当成参考视角、整段扫描偏 30°:
Do not start the sheet at the reference viewpoint - cell 8 is the reference viewpoint.
注:"环绕"指相机绕场景转,不是模型自转(要明写 "it is the CAMERA that orbits");
边界仍是不越过正侧面、绝不露背面。要右→左扫就镜像 LEFT/RIGHT 与锚点描述。
-
合成默认用"前慢后快"节奏(
--timing ramp,已是默认)。
用户要求:开头慢、逐渐加快,让观众看清起始画面。
默认形状--ramp 2.6,0.7,1.8+--duration 70,实测 30 帧停留时长:
180×6 → 170×4 → 160×2 → 150×3 ... → 70×2 → 60×2,总约 4.0 秒。
--timing even是备选(按每格实际变化量分配时间,追求恒定速度感时才用)。 -
旋转/环绕类必须
--loop pingpong。
16 帧只覆盖单程角度,正向播完再镜像回退才无缝(共 30 帧)。用auto会退化成
cycle,接缝处硬跳。原因:旋转被判为pose-change,而halfloop只在
kind == "translation"时生效,loopcut找不到匹配姿态。 -
默认不要逐帧稳像(
--stabilize off,已是默认值)。
这是最容易踩的坑:逐帧稳像给每一帧算一个不同的位移量,"漂移估计误差"直接变成
帧间抖动。实测一张真实环绕图:pose jitter 指数 1.87(原始)→ 2.22(开稳像)→
1.88(关掉)。它非但没让画面更稳,反而制造了抖动。
注意:环绕时主体本来就会横向位移,这是真实视差,不要拿稳像去抹平它。
只需要整体重新居中时用--stabilize anchor——一次性常量偏移,帧间关系完全保留。 -
prompt 里禁止分阶段描述镜头,要用"线性插值 + 每格增量相同"。
写成"第 1–4 格近景、第 5–11 格后拉、第 12–16 格全景",模型就会在阶段交界处猛变
(实测第 3→4 格明显跳变)。正确写法是把 16 格定义成一次连续运动的 16 个等时间采样:
"cell 1 是起点,cell 16 是终点,中间的每一格恰好落在同一条线性插值的等比位置上;
任意相邻两格之间的变化量完全相同;全程没有加速、停顿、快段或慢段"。 -
prompt 必须写明"是相机在环绕,不是模型在自转",并用数字封顶。
否则模型会去转模型本身,几何会崩。同时钉死:固定距离/高度/焦距、地平线不漂、
全程恰好 N 度、分成等步、每格主体完整不出格、场景内每个物体的形状位置颜色不变、
纯色无缝背景、无文字水印边框。
量化封顶比定性描述有效得多,例如:apparent size changes by less than 5%、
max 8% horizontal travel。 -
稳像若真的开启,填充色必须是不透明画布色,否则出黑边。
shift_frame默认fill=(255,255,255,255)。若填透明(0,0,0,0),
后续convert("RGB")会把透明像素压成纯黑——这就是 GIF 边缘那道黑边。
只有真正的透明底素材才传(0,0,0,0)。
同理quantize_frames/export_webp走flatten_rgb()合成到画布色,不要直接 convert。 -
只做 4x4 / 16 帧;GIF 体积上限 3MB。
帧数越多漂移和抖动越明显,成本还翻倍。体积上用户明确 3MB 可接受
(--max-kb 3000)。超出预算时阶梯会先降尺寸再降色数,最低不要跌破 460px/96 色
(那个档位明显发糊);宁可放宽预算,也不要用糊的档位交付。
注意 2K 生成 + 4x4 时单格只有 512px,所以 640px 是放大得来的;细节密集的素材
在 3MB 内通常只能保住 544px 左右,交付时把实际尺寸说清楚。 - 多张参考图时,在 prompt 里写清每张的职责。 实测「外观图 + 剖面图」一起喂时,
模型会默认把剖面图也画进画面。必须显式分工:
Image 1 (exterior) is the MAIN VIEW/Image 2 (cutaway) is the STRUCTURE REFERENCE ONLY - do NOT draw the cutaway, do NOT open up the walls。 -
"锁构图"会直接抹掉旋转——大主体/全出血素材必须放开。(已验收)
环绕之所以"看起来在转",靠的是背景视差(近处划过、远处慢移)+
主体相对画面自然移动。prompt 里写every cell shows the whole subject, complete and uncropped/consistent margins/背景元素形状位置固定,等于把仅有的两条旋转线索封死 ——
模型会走最安全路径:几乎照抄原图 + 微小抖动,观感是"抖/呼吸"而不是"转"。实测(同素材、同模型、同角度,只改这三条写法,g1.jpg):
写法 motion 判定 area_cv 15 步总变化量 锁构图(主体不出格 + 背景不动) translation1.3% 2.225 放开(漂移 + 视差 + 角度锚点) pose-change5.1% 3.315 放开写法三件套(模板见
prompts/orbit60-drift-4x4.txt):
1. 要求主体在画格里漂移,允许跑出画格边界,明写"不许逐格重新居中、不许保持边距不变"。
⚠️ 这一条后来被用户改掉了:默认应改为主体居中、由背景横移体现转动
(硬规则 17)——因为"相机始终瞄准主体"本身就和"主体必须漂移"冲突。
只有主体大到居中会让画面呆住时才回退到漂移版。
2. 要求背景视差,明写"背景元素不许钉死在原位置";
3. 给三个具体视觉锚点:cell 1 = 参考图原视角、cell 8 ≈ 28°(哪一侧开始转出视线)、
cell 16 = 60°(哪一侧转到画面中央、谁进入画面、谁被遮住)。
再加一句失败判据:A sheet in which the subject is in the same orientation in every cell is a FAILED sheet.适用边界(重要):主体小、居中、四周有留白的静物摆件(乐高摆件那类)可以继续锁构图,
60° 已经验证有效;主体大 / 全出血插画 / 特写构图则必须放开,否则转不动。
代价是姿态一致性下降(马腿、披风形状会漂)——这是取舍,不是 bug。
11. 复合运动(环绕 + 推近/拉远)时,QA 的area_cv阈值是误报。
area_cv > 12%会触发verdict: REVIEW / silhouette area varies 47.9% - subject may be reshaping。但只要 prompt 里刻意要求了 dolly,面积大幅变化就是设计要求,不是缺陷。
实测 i1.jpg(60° 环绕 + 推近 30%):area_cv = 49.4%→ QA 报 REVIEW,
而检查表上"由远到近"正是用户想要的效果。
→ 遇到 dolly 时不看area_cv的绝对值,改看它是否单调增长(不允许中途缩回去)。
→ 同理,prompt 里"apparent size < 5%"这条封顶必须删掉并改成"面积匀速单调增长约 N%",
否则和 dolly 直接冲突。
→ 复合运动的代价:帧间步长容易忽大忽小(i1 实测0.213 0.335 0.276 0.303 0.335 0.336 0.768 0.767 0.188 0.330 0.296 0.652 0.606 0.593 0.144,step_cv 1.175),
比单一环绕(step_cv 0.446~0.7)差。追求平顺就做单一运动,追求冲击力才叠复合运动。
12. 素材形态决定交付格式和体积档位——插画类必须放宽或改走 WebP。
实测同参数下 GIF 的表现差异极大:素材 默认 3MB 预算 放开后 WebP 同帧 纯色背景静物(i1 乐高山峰) 480×640 / 255 色 / 3.8MB 同左(已到上限) 651KB 满分辨率 全出血插画(g1 武将) 345×460 / 96 色 / 2.9MB(发糊档) 408×544 / 192 色 / 4.7MB 1576KB 满分辨率 原因:GIF 调色板对大面积平坦色块很省(纯色背景素材 3MB 就能给到 480×640),
对全出血画作极不划算(没有平坦区,3MB 直接把画面打到 345px/96 色,
就是硬规则 8 里标"明显发糊、不要交付"的那一档)。
→ 静物/纯色背景:GIF 3MB 够用,照常交付。
→ 全出血插画/照片:--max-kb 6000起步,并必须同时交付 WebP——
WebP 满分辨率只要 GIF 的 1/3 体积,是这类素材的正确载体,GIF 只留作兼容。
13. 参考图自带水印时,必须在 prompt 里显式禁止复现。
实测有效(g1 武将右下角小红书水印、h1 骑士右下角小红书水印,两版都清干净):
在 REFERENCE 段说明"参考图带水印",并在 OUTPUT 段写
do NOT reproduce it, and do not reproduce any other logo, handle or overlaid image。
只写笼统的no watermarks不够——要把它点名到"参考图右下角那一个"。
14. 主体自身带强运动趋势时,转向会被抵消。(待验证)
实测 h1.jpg(马朝左下方奔驰奔跑)用同一套 v2 写法,只拿到
translation / area_cv 1.5% / 总变化 1.755,明显弱于 g1(静止昂扬姿态,
pose-change / 5.1% / 3.315)。推测模型把"相机绕行"与"主体奔行"混在一起,
两个运动互相抵消。样本数 1,尚未定论;遇到"主体正在飞奔/冲刺"的素材时,
预期转向会打折,可考虑改用"推近"这类不依赖旋转的动效(见硬规则 11)。
15. 场景内角色动作("镜头之外还有个东西要动一下")要单开一段,并配双失败判据。(已验收)
用户可以在环绕之外要求"画面里的某个角色动一下"(j1.jpg:猴子沿山脊走路)。写法:- 单开一段
THE <ROLE> - the only moving thing in the whole scene,写清四件事:
动作方式(如"四条腿交替、一组前摆另一组支撑、尾巴轻摆、头保持平视朝行进方向")、
总位移(如"全程约 1.5 倍身长")、匀速不停、保持同一比例与颜色;
再列出禁止项(不跳、不爬、不站起、不转身、不跑、不变大变小、不换位置、不出画、
不消失、不许出现第二只)。 - 其余段落改成
WHAT MUST NOT CHANGE - everything EXCEPT the <role>:所有道具/建筑/
液体锁死(瀑布不流、船不漂不摇、水面不荡)。 - 失败判据从一条扩成两条:①16 格视角相同 = 失败;②16 格角色姿态相同 = 失败。
- 验证方法:整格对比看不出小角色动没动,必须用
scripts/cell_zoom.py把同一区域
跨格放大并排看(j1 实测猴子从山脊左端走到右峰下,一眼可见)。 - 一个能自证方向的现象:相机往右转会让画面内容向左移,如果小角色在画面里往
相反方向移动,就证明是它自己走的,不是被镜头带过去的。
- 单开一段
-
提示语必须先指名"绕的是谁"(
SUBJECT段),且整份 prompt ≤6000 字符。- 不指名主体,模型就不知道角度绕哪个轴量,容易退化成平移或原地微动。
默认模板已加SUBJECT - the pivot the camera circles段,且必须放在
REFERENCE之前(先指名,再描述)。三个模板(旧版)此前全无此段,
只在相机行里以 "the scene / the subject" 抽象带过——这是被用户点出来的真实缺口。 - 长度上限:实测 6449 字符可用、9213 字符被接口拒收,报
HTTP 400 … prompt 必填且长度为 1—4000 字符。
→ 整份 prompt 控制在 6000 字符以内。
→ 注释 / 变更记录 / 操作说明写进同名.notes.md,不要混进 prompt 文件。
→scripts/apiz_image.py已加两道保护:单独成行的---之后全部截断
(TO OPERATOR / CHANGE LOG 不再发给模型);超限直接本地报错,
并打印[apiz_image] prompt chars: N。
- 不指名主体,模型就不知道角度绕哪个轴量,容易退化成平移或原地微动。
-
主体居中、由背景横移体现转动(已验收)。(用户决定,替换旧的"主体必须漂移"写法)
always aimed at the subject与the subject must DRIFT … may run off the frame edge
互相冲突:始终瞄准轴心的相机,会把主体稳在画面中部,由背景横移体现转动。
所以 prompt 里写的是:主体保持在画面中部、不逐格重新构图、不左右滑动、不变尺寸。- 背景是平涂无特征场时(纯色底、暗色底、无道具,如 k1 的青色场):没有东西可横移 →
转动只能由主体剪影承载。必须逐个写出"该变的地方"(哪一侧躯干朝前、腰带结与
侧缝落点、近侧肩膀遮住多少、飘带投射到画面哪一处),并写上一句
"a frozen pose seen from a new angle is not the same picture"。
只写一句 "silhouette must change" 不够——k1 验收版正是把 3–5 处逐项列出才成立的。 - 模板
prompts/orbit60-l2r-4x4.txt的SUBJECT段与
HOW TO MAKE THE ORBIT VISIBLE第 2 条已按此重写,含金句与占位提示,不要改回去;
填写要点见orbit60-l2r-4x4.notes.md。 - 反例(重要):对同一素材,"主体必须漂移、可以跑出画格"的旧写法(k1 第一版)
被用户否决("很小的动,根本没有旋转");改成主体居中之后才通过。
- 背景是平涂无特征场时(纯色底、暗色底、无道具,如 k1 的青色场):没有东西可横移 →
验收(最重要的一条)
质检数据只是参考,不是标准;出图了就要尽快给用户自己看。 流程必须是:
- 出图即交付,不要拖。 多格图生成完 → 立刻跑
contact_sheet.py出带帧号检查表;
合成完 → 立刻把 GIF / WebP 交给用户。不要为了"先自己判一遍"而延后交付,
也不要拿量化数字当结论。 - QA /
motion_smooth等一切量化数字只作参考,用途是提示"哪里可能有问题"
(比如某一步跳变、边缘不干净),不能用来判定效果好坏:
area_cv、pose在轮廓近似的宽扁场景上会失灵(见「判读 QA 报告」最后一条),
数字说"平移"而肉眼明明在转的情况真实发生过。 - 人是唯一验收标准,AI 自查不算数。 用户看了一眼说"行",才算过。
- 不要在用户验收前就把"经验"写进本文件——没验收的优化是瞎优化。
- 交付时带上"怎么看":给首尾帧对比图(
ab_frames.py)、动作特写(cell_zoom.py),
让用户一眼能判断,而不是丢一个 GIF 让他自己猜。 - 交付时不写效果判断。 不要写"这次转得很明显""效果比上一版好"这类结论——用户明确
批评过 AI 乱下判断。可以说的只有中性事实(帧数、尺寸、体积、某格有跳变这类客观观察)
和中性的"怎么看"(哪张对比图看哪两格)。尤其不要把 QA 的判定转述给用户:
pose-change/translation在这类素材上会整体反报(见「判读 QA 报告」的 k1 反例)。 - 重新生成多格图(花 points 的调用)必须先经用户同意;用户主动说"来个 4K / 重跑"
即视为授权。本地合成(切帧、合成、导出)不花钱,可以直接做。
判读 QA 报告
> 先记住:这一节的数字全部只是参考,不是结论。 用它们找"哪里可能有问题",
> 效果好不好由用户看图和 GIF 判。数字与肉眼冲突时,以肉眼为准。
{
"motion": {"kind": "pose-change", "area_cv": 0.06}, // 环绕=pose-change
"loop": "pingpong",
"border": {"mean_rgb": [139.5, 208.9, 253.0], "near_black_fraction": 0.0},
"output": {"timing": "ramp", "dwell_ms": [180, 180, ...]},
"verdict": "PASS | REVIEW"
}
- 黑边只认
near_black_fraction(接近纯黑的像素占比),不要用亮度判定。
稳像填充透明的特征是 RGBA→RGB 压成(0,0,0),真实主体/阴影/彩色背景三个通道
不会同时全黑。之前的版本按"外圈亮度 < 235"判暗带,遇到蓝色影棚背景(RGB 约
140,209,253,亮度天然只有 200)会 100% 误报,也会把"主体故意出画"误判成黑边。
判定:near_black_fraction > 2%才算真黑边。mean_rgb是给人看的,用来确认外圈
是不是背景色本身。
例外(已实测):素材背景本身就是纯黑时(如 m1 人像表情包,背景是刻意的纯黑底),
near_black_fraction会报到 93.7% 并给出verdict: REVIEW+
pure-black band on the canvas border ... the stabiliser filled with transparent——
这是预期值,不是黑边(该条判据默认"外圈非黑才算正常",黑底素材天然违反它)。
此时改看两点确认:darkest_channel≈ 0 且mean_rgb就是设定背景色 → 忽略该 issue。
注意--bg auto也会报同一类误判:auto从四角采样背景色,剪影式深色头发容易把
采样带偏、主体框跟着飘。背景是纯黑/纯色时直接写死--bg 0,0,0(或对应 RGB),不要用 auto。 area_cv超过 12% → 主体在"变形"而不只是"移动",多半是多主体或复杂动作。
(60° 环绕实测 6% 左右;180° 全环绕会到 50%+,属正常。)
例外:prompt 里刻意要求 dolly(推近/拉远)时,area_cv大是设计要求,不是缺陷
—— 见硬规则 11,此时改看面积是否单调变化。- "到底转没转"——四步判定,顺序不要乱:
1.motion.kind:translation= 只有平移没有换角度;pose-change= 真的换了姿态/视角。
环绕类通常应当拿到pose-change。但它会误报,不要当铁证。
2. 先看步长序列的形态,再看总量。pose step per frame如果是高低交替
(0.117 0.166 0.082 0.168 0.077...),说明在原地来回摆,观感是抖不是转;
应该是平稳推进(缓慢递减、末段收一下也算正常)。
3. 总量只作辅助参考,绝不能单独下结论。 实测 g1 同素材同角度:锁构图写法
translation / 1.3% / 总变化 2.225→ 放开写法pose-change / 5.1% / 3.315。
但 k1 给出了完全相反的结果(下一条),总量大 ≠ 转得好——摆动同样能把总量堆大。
4. 最后用scripts/ab_frames.py把 cell 1 和 cell 16 并排放大看一眼确认。 - k1 反例:QA 的结论可能与人工验收完全相反——一切以肉眼为准。
同一素材(全出血平涂底单主体)先后两版:
| 版本 | QA kind |
pose 总量 | 步长形态 | 用户结论 |
|---|---|---|---|---|
| 第一版 | pose-change |
3.81(更大) | 高低交替 0.098 0.095 0.195 0.190 0.230 0.423 0.199 0.449… |
否决("很小的动,根本没有旋转") |
| 验收版 | translation |
1.53(更小) | 平稳 0.236 0.151 0.060 0.119 0.088 0.051 0.036 0.138 0.106 0.057 0.050 0.062 0.049 0.062 0.270 |
通过 |
原因:这两个指标测的是整幅轮廓。平涂背景的单主体素材,剪影在 16 格里高度相似,
于是"原地来回摆"被判成 pose-change、"真正绕轴扫过"被判成 translation——判定整体反了。
同类还有 j1:QA 报 translation / area_cv 2.8%,而宽扁景观 + 纯白背景从任何角度看轮廓
都差不多,内部结构(瀑布、花树、木船)其实已经大幅换位。
→ 结论:平涂底 / 全出血 / 宽扁轮廓近似 这三类素材,kind、area_cv、pose 总量全部不可用,
只能靠 scripts/ab_frames.py(首尾帧)+ scripts/cell_zoom.py(局部)+ 人眼判。
交付时不要把 QA 结论说给用户听(会误导),只给图和 GIF。
- dwell_ms 在 ramp 模式下应当单调递减;若成片顶在下限 60ms,说明收尾偏慢。
- 判断"抖"来自模型还是合成:motion_smooth.py 跑多格图得 jitter_idx(模型的责任),
再跑合成后的 frames/ 得另一个值。后者明显更高 = 合成环节在恶化,先查 --stabilize。
- 输出尺寸会被体积预算反推。 --max-kb 是硬预算:阶梯会从 --size 起逐级降尺寸、
再降色数,直到塞进预算。所以 3MB 预算下不一定能拿到 640px —— 细节密集的素材
(樱花树、砖墙纹理)压缩率低,可能落到 544px。日志里 gif ... (WxH ...) 报的是实际
落地尺寸,要以它为准(曾有个 bug 把尺寸静默压回源单格宽度,导致 GIF 比同帧的 WebP 还小)。
文件
scripts/apiz_image.py— apiz CLI 包装,生成多格图并落 JSON(--model可切 flare/sunburst)。scripts/giflab.py— 库:切帧、遮罩、稳像(含 anchor)、节奏曲线(even_durations/
ramp_durations)、循环修复、调色板、导出。scripts/make_gif.py— CLI:多格图进,GIF/WebP/MP4/QA 出。scripts/contact_sheet.py— 把多格图切成带帧号的检查表,交给用户逐帧验收。scripts/motion_smooth.py— 量化帧间均匀度(jitter_idx)。scripts/verify_edges.py— 按播放器合成方式量 GIF 边缘,验证黑边是否清净。scripts/ab_frames.py— 抽任意两帧并排 2x 放大,用来肉眼确认"到底转没转"
(判定流程第 4 步;python ab_frames.py sheet.jpg out.png 1 16 4 4 460)。scripts/cell_zoom.py— 跨格放大同一小块区域并排,用来确认场景里的小角色/小物件
是否真的动了(整格对比看不出来的,它一眼就能看)。见硬规则 15。
用法:python cell_zoom.py sheet.jpg out.png "1,5,9,13,16" 0.40,0.12,1.0,0.55 4 4 2.2 monkey
(第 4 个参数是格内相对坐标 x0,y0,x1,y1,取值 0–1)。scripts/compare_clear.py— 跨版本清晰度对照:把两张多格图的同一格归一化到
同一显示尺寸并排,再叠加 2x 局部放大。比"2K vs 4K"这类差异靠它,别凭感觉。
python compare_clear.py A.jpg B.jpg out.png 16 4 4 816 "2K->640" "4K->816"。scripts/make_reel.py— 多个动图 → 一部带标题卡的 MP4 短片。前面所有脚本都是
「一张多格图 → 一个动图」,只有它负责「多个动图 → 一个片子」。用户说
"做成一个视频/作品集/合集"时用它,别去手搓 ffmpeg concat(同尺寸、不同比例、
透明底、像素风这几件事它都处理了,见下节)。scripts/make_voiceover.py— 给短片配 edge-tts 旁白:先合语音量长度,再按语音长度
定每段时长,最后渲染并混音。用户说"加个解说 / 配音 / 旁白 / 介绍一下"时用它,
见「要配旁白时」一节。prompts/char-action-fixed-4x4.txt— 无参考图 / 单角色动作模板(B 类):相机锁死、
角色自己完成一套连续动作、特效为唯一可变元素。含CHARACTER/LOOK/THE ACTION/
CAMERA - LOCKED/WHAT MUST NOT CHANGE/FAILURE段与<<< >>>占位。
本体 5800 字符(贴近 6000 上限,填充后必须复测)。填写要点与防坑清单在
char-action-fixed-4x4.notes.md。首个实例:唐僧 + 九环锡杖聚气挥出金光,
4:3 / 2K / Flare,prompt 5204 字符 →D:/image25/gif-lab/out/tangmonk/。
效果尚未经用户验收,暂不并入硬规则。prompts/char-action-fixed-4x4.notes.md— 上者的填写说明(怎么指名角色、道具怎么当刚体
写、动作为什么要写成"一条弧 + 三锚点"、为什么用cycle而不是pingpong)。不进 prompt。prompts/face-expression-loop-4x4.txt— 有参考图 / 机位锁死 / 只变表情模板(D 类):
头肩特写,用于表情包。含SUBJECT/CAMERA - LOCKED/LOOK/THE EXPRESSION ARC
(5 个 ANCHORS)/WHAT MAY CHANGE / WHAT MUST NOT/FAILURE段与<<< >>>占位。
本体 5917 字符(贴近 6000 上限,填充后必须复测)。填写要点在
face-expression-loop-4x4.notes.md。首个实例:m1.jpg 夸张表情包,
3:4 / 2K / Flare,prompt 5305 字符 →D:/image25/gif-lab/out/face-expr/。
效果尚未经用户验收,暂不并入硬规则。prompts/face-expression-loop-4x4.notes.md— 上者的填写说明:四类动效对照、
为什么 D 类机位要锁得最死、为什么 D 类允许分节拍(与硬规则 5 的关系)、
左右方位为什么必须用viewer's left、以及两个必须记住的坑
(--trim必须用none、黑底用--bg 0,0,0不要用auto)。不进 prompt。prompts/orbit60-l2r-4x4.txt— 默认模板:左 30° → 右 30°(60° 相机环绕,
参考视角在表中间),见硬规则 1。含SUBJECT段(硬规则 16)、
HOW TO MAKE THE ORBIT VISIBLE四条(硬规则 10 / 17)、动作段(硬规则 15)的
<<< >>>占位,共 9 组。模板本体 5886 字符(限 6000,见硬规则 16)。
填写要点与操作说明在orbit60-l2r-4x4.notes.md(不进 prompt)。prompts/orbit60-4x4.txt— 旧默认:单向 0° → 60° 相机环绕(历史版本,
转角不如"左→右"好读,除特殊需要否则用 l2r 那版)。prompts/orbit60-drift-4x4.txt— 主体漂移版(历史版本,别默认用):60° 环绕 + 放开
构图锁(主体漂移 + 背景视差 + 角度锚点)。硬规则 10 的实测对照就是它跑出来的(g1 已验收)。
用户后续已把默认改成主体居中、背景横移(硬规则 17),默认请用 l2r 那版;
仅当"主体极大、居中会让画面呆住"时才回退到它。文件末尾有---之后的操作说明。prompts/orbit60-dollyin-4x4.txt— 复合运动:60° 环绕 + 由远到近推近约 30%,
见硬规则 11。注意它没有"尺度变化 <5%"封顶,改成"面积匀速单调增长"。prompts/orbit60-two-ref-4x4.txt— 双参考图版(外观主视角 + 结构参考),见硬规则 9。prompts/turntable-180-4x4.txt— 180° 环绕(单主体产品,历史版本)。prompts/orbit-reveal-180-16x9.txt— 宽幅场景 180° 环绕 + 后拉揭示,16:9(历史版本)。README.md— 面向非技术使用者的白话说明(无命令、无参数),第二节是五类效果的
案例集(A 环绕 / B 角色动作 / C 速度型 / C2 像素透明 / D 表情包),每类一张真实成品的
GIF + "你会提供什么 / 你可以这么说 / 适合什么"。用户是要"发给朋友/同事看"的场合,
给这份,不要给 SKILL.md。cases/— README 案例集用的成品动图(约 7.5MB,纯展示用,不是素材):
A-orbit-60-l2r.gif(218×320/30 帧,原README-demo.gif)、
B-character-action.gif(640×480,唐僧锡杖聚气)、
C-scroll-flight.gif(640×480,孙行者翻筋斗云)、
C2-arcade-transparent.gif+.webp(512×384,街机像素透明底,webp 是真 alpha 版)、
C2-arcade-pixel128.gif(128×96 老街机颗粒)、
D-expression-loop.gif(180×240 表情包)。
新增案例就往这里放并同步 README 第二节的表格;A 类那张就是早期的README-demo.gif,
不要在原位置重复放一份。