基于 Pillow + ffmpeg 的竖屏短视频模板规范。
- 中文全称:氛围底片手写叠字
- 中文简称:治愈叠字
- 英文名:HealingOverlay
「氛围底片手写叠字」指全画幅氛围底片上,硬笔书法逐字写出问句并署名的竖屏短视频。
代码与资源:字体、
render_overlay.py与目录约定打包在 healing-overlay-template.zip 中(文末附下载)。手册写流程与写死参数;实现以工程包为准。
一、视频规格
| 项目 | 规范 |
|---|---|
| 分辨率 | 1080 × 1920(9:16 竖屏,写死) |
| 帧率 | 推荐 30 fps(可按底片实际帧率微调,成片统一一种帧率) |
| 时长 | 跟随底片(叠字写完后至少再留约 0.5s 静置;不足则循环或截取底片) |
| 编码 | 推荐 H.264 + AAC,色彩空间 bt709 / TV range;CRF 可按体积微调(成片母带偏高质量,分发可再压) |
| 格式 | .mp4 |
| 色彩处理 | 保留底片原色,不做滤镜、不做统一调色 |
画幅写死;帧率与编码写成推荐,允许按素材与平台体积要求微调,但同一批次成片参数应一致。
二、画面结构
2.1 核心原则
- 底片铺满整幅 1080×1920,不做「上字下片」分区。
- 问句两行(或等价分行)+ 署名字行,组成一个文字块,整体垂直居中(块视觉中心 ≈
y = 960)。 - 问句左对齐;署名右对齐;左右内容边距写死为 150px。
- 动画为硬笔感 左→右 wipe 逐字写出,不是淡入、不是打字机光标。
┌─────────────────────────────┐ 1080
│ ↑ 上安全区(平台 UI) │
│ │
│ 哪一种天气, │ ← 问句行 1,左对齐 x=150
│ 会让你突然想对自己好一点? │ ← 问句行 2,左对齐 x=150
│ @高老师的分享局 │ ← 署名,右对齐 右缘 x=930
│ │
│ (文字块垂直中心 ≈ 960) │
│ │
│ ↓ 下安全区(文案条 / 底栏) │
└─────────────────────────────┘ 1920
2.2 安全区(写死)
对齐抖音 / 视频号常见 UI 避让,语义文字不得进入下列禁区:
| 边 | 常量 | px | 说明 |
|---|---|---|---|
| 左 | SAFE_L | 150 | 与问句左边距一致 |
| 右 | SAFE_R | 150 | 内容右缘 ≤ 1080 - 150 = 930 |
| 上 | SAFE_T | 220 | 状态栏、账号信息行 |
| 下 | SAFE_B | 360 | 文案条、音乐栏、底栏控件 |
内容容器(文字与可选轻量底板):
SAFE_BOX = (150, 220, 930, 1560) # L, T, R, B
SAFE_W = 780 # 930 - 150
SAFE_H = 1340 # 1560 - 220
- 问句与署名的字形轮廓必须落在
SAFE_BOX内。 - 软阴影可略微溢出约 8–12px;实心笔画与字面不得出界。
- 本模板文字块垂直居中于
y ≈ 960,天然落在上下安全区之间;禁止把整块文字贴顶或贴底。
2.3 布局常量(写死)
| 项目 | 值 |
|---|---|
| 画布 | W=1080, H=1920 |
| 问句左边距 | x = 150(行首对齐) |
| 署名右缘 | x = 930(右对齐) |
| 文字块垂直中心 | v_mid ≈ 960(H // 2) |
| 内容最大宽 | 780(W - 2 × 150) |
| 问句行距 | 行盒高度之外再加约 24px 行间空隙(按字体 metrics 算行高) |
两行问句均从 x=150 起笔;署名单独一行,按字宽计算后使最后一个字形右缘落在 930。
三、字体与字色
3.1 字体(写死)
| 项目 | 规范 |
|---|---|
| 字体 | 霞鹜文楷(LXGW WenKai)Regular |
| 文件 | 工程内 fonts/LXGWWenKai-Regular.ttf(或等价路径,须可被 Pillow ImageFont.truetype 加载) |
| 禁止 | 换成其它楷体 / 硬笔字体冒充;本规范锁定这一款 |
3.2 字号(写死)
| 元素 | 字号 | 对齐 |
|---|---|---|
| 问句(两行) | 58px | 左对齐,x=150 |
署名 @高老师的分享局 | 36px | 右对齐,右缘 x=930 |
问句在 58px、内容宽 780 下须整行可排下;若换文案后超宽,优先改分行或缩短文案,不要擅自改字号与边距。
3.3 字色(推荐,可微调)
| 用途 | 推荐值 | 说明 |
|---|---|---|
| 字面填充 | #FFF8F0 / rgba(255,248,240,255) | 浅暖白,适合多数氛围底 |
| 轻描边 | rgba(30,24,18,90) 约 1px 八邻域 | 提高暗底可读性 |
| 投影 | rgba(20,16,12,160),模糊约 2.5px,偏移约 (2,3) | 与底片分离 |
底片偏亮时,可将填充略降亮或略增描边不透明度;不要改成高饱和彩色字。默认以浅色字 + 轻阴影为准。
3.4 署名(写死)
画内署名固定为:
@高老师的分享局
字号 36px,霞鹜文楷 Regular,右对齐至 x=930,与问句同一文字块、同一垂直居中逻辑。
四、时间轴与逐字动画
4.1 硬性节奏
| 阶段 | 规范 |
|---|---|
| 开场空镜 | 写死 2.0s:t ∈ [0, 2.0) 画面只有底片,无字 |
| 问句写出 | t = 2.0 起,按公式逐字左→右 wipe |
| 问句与署间隔 | 0.35s 静置(问句末字写完 → 署名首字开始) |
| 署名写出 | 同样公式逐字 wipe |
| 写完后 | 保持终态叠字至片尾 |
成片实测对照(示例问句 19 字 + 署名 8 字):
| 节点 | 时刻 |
|---|---|
| 问句首字开始 | 2.000s |
| 问句写完 | 4.880s |
| 署名开始 | 5.230s |
| 署名写完 | 6.460s |
4.2 单字时长公式(可算)
常量:
DELAY_START = 2.0 # 开场空镜,写死
CHAR_STAGGER = 0.15 # 相邻字开始时刻间隔(秒)
CHAR_WIPE = 0.18 # 单字左→右 wipe 时长(秒)
PAUSE_BEFORE_AT = 0.35 # 问句完成到署名开始
设问句字符序列为 Q[0..n-1](含标点,按 Unicode 码点计字),署名为 A[0..m-1]:
Q_start[i] = DELAY_START + i * CHAR_STAGGER
Q_done[i] = Q_start[i] + CHAR_WIPE
Q_done = Q_done[n-1] # = DELAY_START + (n-1)*CHAR_STAGGER + CHAR_WIPE
A_start[j] = Q_done + PAUSE_BEFORE_AT + j * CHAR_STAGGER
A_done[j] = A_start[j] + CHAR_WIPE
A_done = A_done[m-1]
校验(本规范示例文案):
n = len("哪一种天气,会让你突然想对自己好一点?") # 19
m = len("@高老师的分享局") # 8
Q_done = 2.0 + 18*0.15 + 0.18 = 4.88
A_start = 4.88 + 0.35 = 5.23
A_done = 5.23 + 7*0.15 + 0.18 = 6.46
换文案时只改 Q / A 字符串,用公式重算;不要改 DELAY_START。
4.3 Wipe 动画(左→右)
- 每个字先完整栅格化为 RGBA sprite(含描边与阴影)。
- 在字形 alpha 包围盒内做 水平 wipe:进度
p ∈ [0,1],可见区域从左到右扩大。 - 进度缓动推荐
ease_out_cubic:1 - (1-p)³。 - 边缘软过渡宽度约
max(6, glyph_width * 0.18)px,避免硬切刀口。 - 字与字之间按
CHAR_STAGGER错开开始,形成连续手写感;同一时刻可有多个字处于 wipe 中。
伪代码:
def ease_out_cubic(t: float) -> float:
t = max(0.0, min(1.0, t))
return 1.0 - (1.0 - t) ** 3
def char_progress(t: float, start: float, wipe: float = CHAR_WIPE) -> float:
return (t - start) / wipe # 再 clamp 到 [0,1] 后送入 wipe
五、氛围底片
| 项目 | 规范 |
|---|---|
| 来源 | 任意竖屏氛围素材(实拍 / 库存均可) |
| 画幅 | 建议已是 9:16;若不是,先 crop/scale 成 1080×1920 再叠字 |
| 色彩 | 保留原色,禁止统一滤镜、禁止强对比拉伸 |
| 音轨 | 可保留底片原声;也可无音轨(本规范不强制 BGM) |
| 内容 | 不遮挡中部安全区关键信息;文字区尽量避开人脸五官与大面积高亮招牌字 |
示例成片说明(占位,不挂真实链接):
示例成片:雨天氛围底片 + 问句「哪一种天气,会让你突然想对自己好一点?」+ 署名
@高老师的分享局,按本规范叠字导出。工程包见文末下载 healing-overlay-template.zip(含字体与render_overlay.py);成片样例可选,本站暂不挂 mp4。
六、制作流程(Pillow + ffmpeg)
不单开 Agent 清单;按下列步骤执行即可复现。
6.1 目录约定
工程包 healing-overlay-template.zip 解压后即为下列结构(顶层文件夹名 healing-overlay-template/,与下表路径一致;亦可将目录改名为 healing-overlay/)。
healing-overlay/ # 项目英文名 HealingOverlay,中文名氛围底片手写叠字
├── fonts/
│ └── LXGWWenKai-Regular.ttf # 写死字体
├── source/
│ └── base.mp4 # 氛围底片(已是或将转为 1080×1920)
├── frames/ # 透明叠字序列 frame_0000.png …
├── render_overlay.py # Pillow:排版 + wipe 出帧
├── out/
│ ├── final.mp4 # 成片
│ ├── first_frame.png # 封面选中帧(9:16,可选)
│ └── cover_3x4.png # 3:4 封面(可选)
└── README.md # 可选:指向本规范
6.2 Step 1 · 准备底片
# 统一到 1080×1920;帧率推荐 30(可按素材微调)
ffmpeg -y -i source/raw.mp4 \
-vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1,fps=30,format=yuv420p" \
-c:v libx264 -crf 18 -pix_fmt yuv420p \
-colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv \
-c:a aac -movflags +faststart \
source/base.mp4
用 ffprobe 确认分辨率与色彩标签。保留原色,不要加 eq/curves 等滤镜。
6.3 Step 2 · 排版与出叠字帧(Pillow)
render_overlay.py 关键职责:
- 用
LXGWWenKai-Regular.ttf以 58 / 36 量问句与署名。 - 问句两行左对齐
x=150;署名右对齐到x=930;整块垂直居中使v_mid ≈ 960。 - 按第四节公式算每个字的
start;对每一视频帧t = fi / fps合成透明 RGBA。 - 导出
frames/frame_%04d.png(与底片同帧率、同帧数或覆盖至A_done之后)。
布局自检(出片前打印):
block L=150 R=930 v_mid≈960
Q_SIZE=58 AT_SIZE=36
first_start=2.000 …
任一项偏离写死值,先改脚本再渲染。
6.4 Step 3 · 合成成片(ffmpeg)
透明 PNG 序列叠到实拍底片上(示例):
FPS=30
ffmpeg -y \
-i source/base.mp4 \
-framerate $FPS -i frames/frame_%04d.png \
-filter_complex "[0:v][1:v]overlay=0:0:format=auto,format=yuv420p" \
-c:v libx264 -crf 18 -pix_fmt yuv420p \
-colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv \
-c:a copy \
-shortest \
-movflags +faststart \
out/final.mp4
- 无音轨底片可加
-an。 - 需要更小体积时,在母带合格后再单独压一版分发文件,勿直接把母带 CRF 拉得过高而糊字。
6.5 Step 4 · 验收
| 检查项 | 标准 |
|---|---|
| 分辨率 | 1080×1920 |
| 开场 | 前 2.0s 无字 |
| 对齐 | 问句左缘 150;署名右缘 930;块中心 ≈960 |
| 字号 | 问句 58;署名 36 |
| 字体 | 确认为霞鹜文楷 Regular |
| 安全区 | 字形未入上 220 / 下 360 禁区 |
| 动画 | 左→右 wipe,非淡入 |
| 色彩 | 底片观感与源片一致(原色) |
| 署名 | 文案为 @高老师的分享局 |
七、关键实现要点(摘录)
7.1 右对齐署名
AT = "@高老师的分享局"
at_font = ImageFont.truetype(FONT_PATH, 36)
at_w = at_font.getlength(AT)
at_x = 930 - at_w # 右缘落在 930
7.2 垂直居中文字块
V_CENTER = 1920 // 2 # 960
# total_h = 各行行高之和 + 行距
y0 = int(round(V_CENTER - total_h / 2))
7.3 单字开始时刻
DELAY_START, CHAR_STAGGER, CHAR_WIPE = 2.0, 0.15, 0.18
starts = [DELAY_START + i * CHAR_STAGGER for i in range(n)]
# 末字完成 = starts[-1] + CHAR_WIPE
完整 wipe / 描边 / 投影实现以工程内 render_overlay.py 为准;本规范约束数值与行为,不绑定某一份脚本文件名。
八、快速修改对照表
| 想改什么 | 改哪里 | 参数 / 规则 |
|---|---|---|
| 开场空多久 | DELAY_START | 保持 2.0(本规范写死) |
| 手写快慢 | CHAR_STAGGER / CHAR_WIPE | 默认 0.15 / 0.18;改后用公式重算 |
| 问句文案 | 字符串 Q + 分行 | 左对齐 150;超宽先改分行,不改边距 |
| 署名字号 | AT_SIZE | 保持 36 |
| 问句字号 | Q_SIZE | 保持 58 |
| 左右边距 | MARGIN_LR | 保持 150(右缘恒为 930) |
| 垂直位置 | V_CENTER | 保持 960 |
| 字色 | 填充 / 描边 / 阴影 | 推荐浅色,可按底片明暗微调 |
| 底片 | source/base.mp4 | 任意竖屏氛围素材,原色 |
| 帧率 | ffmpeg fps= | 推荐 30,可微调但需与 PNG 序列一致 |
| 体积 | 成片 CRF / 二次压制 | 母带质量优先,分发再压 |
| 封面 / 首帧 | 成片选帧 N(须 ≥ A_done) | 裁 crop=1080:1440:0:240;有声须 adelay |
九、注意事项
- 不要做成 CaptionReel 式「上半字卡、下半影像」;本模板是全画幅底片 + 中部叠字。
- 不要把开场空镜改成 0;2.0s 是写死节奏,用于先看氛围再出字。
- 不要用淡入、缩放弹入代替左→右 wipe;手写感依赖 wipe 方向与错峰。
- 不要更换霞鹜文楷 Regular;字号与边距按该字体标定。
- 不要把署名改成其它账号;规范写死
@高老师的分享局。 - 换问句后必须重算时间轴,并用
timing_proof(或等价日志)核对Q_done/A_done。 - 合成时保持 bt709 / yuv420p,避免把底片洗成错误色彩范围。
- 安全区上 220 / 下 360 为平台 UI 避让;左右 150 同时是排版边距,二者一并遵守。
- 本规范不覆盖发布标题与话题标签;发布文案另案处理。
- 封面选帧须在问句与署名全部写完之后(
t ≥ A_done);3:4 裁切后问句两行与署名@高老师的分享局须完整可见,不得带病上线。详见第十节。 - 模板工程包附在本手册:healing-overlay-template.zip(字体 +
render_overlay.py+ 目录约定);成片样例 mp4 仍可选,不强制挂链。
十、封面与首帧
封面从成片取一帧,不另做设计。流程对齐 字卡影 · 封面与首帧 / 主片卡,并按本模板约定。
- 选帧(硬性):必须在问句与署名全部写完之后再选(
t ≥ A_done)。示例文案A_done ≈ 6.46s,30fps 下N ≥ ceil(A_done × FPS)(约 ≥ 194)。禁止选开场空镜t < 2.0(尚无字)或 wipe 未完成的帧。在写完终态之上,优先挑底片氛围清楚、文字对比可读的一帧。 - 比例:平台要求 3:4。以画面中心为原点,上下等量裁切:1080×1920 → 1080×1440,上下各裁 240px(
crop=1080:1440:0:240,保留区 y = 240–1680)。 - 硬性检查(3:4 安全区):裁切后的
cover_3x4.png上,整段问句 + 署名@高老师的分享局必须完整可见,不得被上下裁切吃掉。本模板文字块垂直居中 ≈960,通常落在保留区内;若换文案后块过高导致缺字,须先改分行 / 缩短文案再出封面,不得带病上线。 - 首帧:把选中帧的完整 9:16 画面插到视频最前面,只占 1 帧,用于平台自动取首帧作默认封面。平台按 3:4 居中裁切后,结果就是封面图。成片总帧数因此多出 1 帧。
本模板常用 Pillow + ffmpeg 管线,成片路径写死为 out/final.mp4;封面命令一律对成片取帧,不要用 Remotion still。有音轨时必须 adelay;无音轨跳过 adelay,见 10.3。
10.1 取帧与 3:4 封面
IN=out/final.mp4
FPS=30
N=200 # 示例:须 ≥ ceil(A_done*FPS);示例文案约 ≥194
ffmpeg -i $IN -vf "select=eq(n\,$N)" -frames:v 1 out/first_frame.png
ffmpeg -i out/first_frame.png -vf "crop=1080:1440:0:240" out/cover_3x4.png
# 目视检查 cover_3x4.png:问句两行 + 署名完整可见
10.2 插入首帧(有音轨 · 默认)
氛围底片常保留原声。插帧后须把音频整体后延 1 帧,否则音画错位。用 adelay 按采样数延迟(采样率 ÷ FPS,例如 48 kHz / 30fps → 1600 采样),all=1 作用于所有声道;不做任何音量处理。PNG 与成片统一成 yuv420p(TV 范围),再用 setpts=N/(FPS*TB) 重排时间戳,避免插入帧与主片首帧时间戳重叠被丢帧。
IN=out/final.mp4
FPS=30
N=200 # 与 10.1 同一选帧
SR=$(ffprobe -v error -select_streams a -show_entries stream=sample_rate -of csv=p=0 $IN)
D=$((SR / FPS)) # 1 帧对应的采样数
ffmpeg -i $IN -vf "select=eq(n\,$N)" -frames:v 1 out/first_frame.png
ffmpeg -i out/first_frame.png -vf "crop=1080:1440:0:240" out/cover_3x4.png
ffmpeg -loop 1 -framerate $FPS -i out/first_frame.png -i $IN \
-filter_complex "\
[0:v]scale=out_range=tv,format=yuv420p,setsar=1,fps=$FPS,trim=end_frame=1,setpts=PTS-STARTPTS[f];\
[1:v]format=yuv420p,setsar=1,fps=$FPS,setpts=PTS-STARTPTS[m];\
[f][m]concat=n=2:v=1:a=0,setpts=N/($FPS*TB)[v];\
[1:a]adelay=delays=${D}S:all=1,asetpts=PTS-STARTPTS[a]" \
-map "[v]" -map "[a]" -r $FPS \
-c:v libx264 -crf 16 -pix_fmt yuv420p -color_range tv \
-c:a aac -b:a 192k -ar $SR -movflags +faststart out/final_with_cover.mp4
- 复核:
ffprobe -count_frames帧数 = 原帧数 + 1;- 音频时长 ≈ 原音频时长 + 1 帧,与视频对齐;响度与原片一致;
- 首帧居中裁 3:4 后与
out/cover_3x4.png一致;第 2 帧与原片第 1 帧一致; out/cover_3x4.png上问句两行 + 署名完整可见(硬性)。
10.3 无音轨
若底片无音轨(或成片已 -an),插帧时跳过 adelay,只做取帧、裁封面、插首帧:
IN=out/final.mp4
FPS=30
N=200
ffmpeg -i $IN -vf "select=eq(n\,$N)" -frames:v 1 out/first_frame.png
ffmpeg -i out/first_frame.png -vf "crop=1080:1440:0:240" out/cover_3x4.png
ffmpeg -loop 1 -framerate $FPS -i out/first_frame.png -i $IN \
-filter_complex "\
[0:v]scale=out_range=tv,format=yuv420p,setsar=1,fps=$FPS,trim=end_frame=1,setpts=PTS-STARTPTS[f];\
[1:v]format=yuv420p,setsar=1,fps=$FPS,setpts=PTS-STARTPTS[m];\
[f][m]concat=n=2:v=1:a=0,setpts=N/($FPS*TB)[v]" \
-map "[v]" -r $FPS \
-c:v libx264 -crf 16 -pix_fmt yuv420p -color_range tv \
-an -movflags +faststart out/final_with_cover.mp4
- 复核:帧数 = 原帧数 + 1;成片仍无音轨(
-an);cover_3x4.png问句与署名完整可见。
十一、与同系列其它短视频模板的区别
| 氛围底片手写叠字 HealingOverlay | 字卡影 CaptionReel | 贴文卡 PostCard | 杂志撕纸风 MG | |
|---|---|---|---|---|
| 结构 | 全画幅氛围底 + 中部手写叠字 | 上半文字卡、下半影像 | 黑底上的柔白帖文卡 | 多镜撕纸 MG 拼接 |
| 常见技术栈 | Pillow + ffmpeg | Remotion | Remotion | Pillow + ffmpeg |
| 文字呈现 | 硬笔左→右 wipe | 淡入 + 轻缩放 | 段落淡入 | 标签 / 结论句贴图 |
| 左右边距 | 150 / 右缘 930 | 100 | 卡片宽 880 居中 | L64 / R128 |
| 开场 | 强制空 2.0s | 文字约 1s 内入场 | 约 2s 内淡入完 | 随旁白分镜 |
本文档作为 HealingOverlay 氛围底片手写叠字模板的设计与制作规范,持续迭代中。
模板工程包下载
⬇︎ healing-overlay-template.zip(约 11.1MB,14 个文件)——内含霞鹜文楷 Regular、render_overlay.py(排版+wipe)、空的 source/frames/out 目录与 example/script.txt。解压后按本手册第六节执行。