wjs-burning-subtitles

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

wjs-burning-subtitles

wjs-burning-subtitles

Video + SRT → video with subtitles. Also the final-encode stage for the localization pipeline: takes a video, an optional dub track from
/wjs-dubbing-video
, and an optional SRT to burn, and produces the upload-ready MP4 in one ffmpeg pass. No cascade of decodes/re-encodes.
视频 + SRT → 带字幕的视频。同时也是本地化流程的最终编码阶段:接收视频、可选的来自
/wjs-dubbing-video
的配音轨道,以及可选的待烧录SRT字幕,通过一次ffmpeg编码生成可直接上传的MP4文件,无需多次解码/转码。

When to use

使用场景

  • User has an SRT and wants it always-visible on the video (burn-in for 微信视频号 / 抖音 / WeChat — players that won't honor embedded subtitle tracks).
  • User wants a togglable subtitle track (soft-mux) for QuickTime / VLC / IINA / mobile players that support
    mov_text
    .
  • Final composite after
    /wjs-dubbing-video
    : burn target-language subs + mix dub over original-as-bed in one encode.
  • 用户拥有SRT字幕,希望字幕始终显示在视频上(为微信视频号/抖音/微信等不支持内嵌字幕轨道的平台制作硬字幕)。
  • 用户希望生成可切换的字幕轨道(软封装),用于QuickTime/VLC/IINA/支持
    mov_text
    的移动播放器。
  • /wjs-dubbing-video
    之后进行最终合成:一次编码完成目标语言字幕烧录+配音与原背景音混合。

When NOT to use

不适用场景

  • No SRT yet → run
    /wjs-transcribing-audio
    then
    /wjs-translating-subtitles
    first.
  • HTML/CSS captions (kinetic, per-word highlights, custom fonts) on a clip composed in HyperFrames → use
    /wjs-overlaying-video
    instead. Don't mix libass burn-in with HyperFrames captions on the same output.
  • The "subtitles" are actually motion graphics (animated callouts, lower-thirds with logos, kinetic typography) → that's
    /wjs-overlaying-video
    , not this skill.
  • 还没有SRT字幕 → 先运行
    /wjs-transcribing-audio
    /wjs-translating-subtitles
  • 在HyperFrames中制作的带HTML/CSS样式的字幕(动态字幕、逐词高亮、自定义字体)→ 请使用
    /wjs-overlaying-video
    ,不要在同一输出视频中混合使用libass烧录字幕和HyperFrames字幕。
  • “字幕”实际上是动态图形(动画标注、带logo的下三分之一字幕、动态排版)→ 这属于
    /wjs-overlaying-video
    的功能范畴,而非本工具。

The 3 modes of
render.py

render.py
的三种模式

scripts/render.py
auto-detects mode from flags:
  1. Subtitles only
    --video + --srt
    → re-encodes video with burned subs, original audio passes through.
  2. Dub only
    --video + --dub
    → keeps original video stream; replaces or mixes the audio track.
  3. Full localized cut
    --video + --srt + --dub
    → burns subs AND mixes dub. By default keeps original audio at low volume as a "bed" under the dub (set
    --bed-volume 0
    or
    --no-original-audio
    to drop it).
Burn-in requires an ffmpeg built with libass. The script auto-downloads a static libass-enabled build from evermeet.cx into
/tmp/ff_bin/
on first use if needed.
scripts/render.py
会根据参数自动检测模式:
  1. 仅字幕模式
    --video + --srt
    → 重新编码视频并烧录字幕,原音频直接保留。
  2. 仅配音模式
    --video + --dub
    → 保留原视频流;替换或混合音频轨道。
  3. 完整本地化剪辑模式
    --video + --srt + --dub
    → 烧录字幕并混合配音。默认将原音频以低音量作为“背景音”混合在配音下方(可设置
    --bed-volume 0
    --no-original-audio
    来移除原音频)。
烧录字幕需要编译了libass的ffmpeg。脚本首次运行时,若默认ffmpeg缺少libass,会自动从evermeet.cx下载带libass的静态构建包到
/tmp/ff_bin/

Soft-mux (togglable subtitle track)

软封装(可切换字幕轨道)

Player apps can show/hide. Works with any
ffmpeg
build — does not need libass:
bash
ffmpeg -i input.mp4 -i input.zh-CN.srt \
  -map 0:v -map 0:a -map 1:0 \
  -c:v copy -c:a copy -c:s mov_text \
  -metadata:s:s:0 language=zho -metadata:s:s:0 title="中文" \
  output.mp4
This is fast (stream-copy) and reversible. Use it when:
  • Target platform supports embedded subs (YouTube auto-detects; VLC/QuickTime honors).
  • User wants viewers to be able to toggle off.
  • You don't want to re-encode the video.
render.py --video IN.mp4 --srt SUB.srt --soft-mux
runs this path.
播放器应用可显示/隐藏字幕。适用于任何版本的
ffmpeg
——无需libass:
bash
ffmpeg -i input.mp4 -i input.zh-CN.srt \
  -map 0:v -map 0:a -map 1:0 \
  -c:v copy -c:a copy -c:s mov_text \
  -metadata:s:s:0 language=zho -metadata:s:s:0 title="中文" \
  output.mp4
此方式速度快(直接流复制)且可逆。适用于以下场景:
  • 目标平台支持内嵌字幕(YouTube可自动识别;VLC/QuickTime支持)。
  • 用户希望观看者可以关闭字幕。
  • 不想重新编码视频。
运行
render.py --video IN.mp4 --srt SUB.srt --soft-mux
即可执行此流程。

Hardcoded burn-in (always visible, libass)

硬编码烧录(始终可见,需libass)

Required for WeChat/抖音/朋友圈 etc. where the player will not honor embedded subtitle tracks.
适用于微信/抖音/朋友圈等不支持内嵌字幕轨道的平台。

Verify libass is available BEFORE promising burn-in

烧录前先确认libass可用

bash
ffmpeg -filters 2>&1 | grep -E "subtitles|^.. ass "
If neither
subtitles
nor
ass
shows up, the build lacks libass. Homebrew's default
ffmpeg
formula is often stripped (no
--enable-libass
, no
--enable-libfreetype
, no
drawtext
). Don't waste time fighting the comma-escaping inside
force_style
— it will fail with
No such filter: 'subtitles'
no matter how the shell quotes it.
bash
ffmpeg -filters 2>&1 | grep -E "subtitles|^.. ass "
如果未显示
subtitles
ass
,说明当前ffmpeg构建包缺少libass。Homebrew默认的
ffmpeg
配方通常是精简版(未启用
--enable-libass
--enable-libfreetype
drawtext
)。不要浪费时间调整
force_style
中的逗号转义——无论如何转义,都会提示
No such filter: 'subtitles'
错误。

Fastest fix on macOS — drop in a static build, no system changes

macOS上最快的解决方法——直接使用静态构建包,无需修改系统

bash
curl -fsSL -o /tmp/ff.zip https://evermeet.cx/ffmpeg/getrelease/zip
unzip -o /tmp/ff.zip -d /tmp/ff_bin >/dev/null
FF=/tmp/ff_bin/ffmpeg
$FF -version | grep -oE -- "--enable-(libass|libfreetype)"
Then use
$FF
instead of
ffmpeg
for the render. The brew binary is fine for everything else (probe, audio extraction, soft-mux).
render.py
does this auto-fallback if its default ffmpeg lacks libass.
bash
curl -fsSL -o /tmp/ff.zip https://evermeet.cx/ffmpeg/getrelease/zip
unzip -o /tmp/ff.zip -d /tmp/ff_bin >/dev/null
FF=/tmp/ff_bin/ffmpeg
$FF -version | grep -oE -- "--enable-(libass|libfreetype)"
之后使用
$FF
代替
ffmpeg
进行渲染。Homebrew的二进制文件可用于其他操作(探测、音频提取、软封装)。
render.py
会在默认ffmpeg缺少libass时自动执行此回退方案。

Burn-in render with style overrides

带样式覆盖的烧录渲染

🛑 Checkpoint — confirm before full-render. Burn-in re-encodes the entire video (minutes of CPU on a 5-min clip). Before kicking it off:
  1. Render only the first 30s with
    -t 30
    for a fast preview.
  2. Extract a frame from the longest-line cue (see Fontsize calibration below) and Read it.
  3. Show the user the preview frame + the cue text, ask: "字号/字体/边距 OK 吗?OK 才跑全片。" Wait for explicit confirmation.
Skip the checkpoint only if the user has already approved a full render of this exact video at this exact font config in the same conversation.
bash
$FF -i input.mp4 \
  -vf "subtitles=input.zh-CN.srt:force_style='Fontname=PingFang SC\,Fontsize=12\,PrimaryColour=&H00FFFFFF\,OutlineColour=&H00000000\,BorderStyle=1\,Outline=2\,Shadow=1\,MarginL=20\,MarginR=20\,MarginV=40'" \
  -c:v libx264 -crf 18 -preset medium -pix_fmt yuv420p \
  -c:a copy output.mp4
Inside
force_style
, escape every comma as
\,
(the filter graph parser eats the bare comma as a chain separator). All other special chars are fine.
🛑 检查点——全片渲染前请确认。烧录字幕需要重新编码整个视频(5分钟的视频可能需要数分钟CPU时间)。开始全片渲染前:
  1. 使用
    -t 30
    仅渲染前30秒,快速预览效果。
  2. 提取字幕最长行对应的帧(见下方字号校准)并查看。
  3. 向用户展示预览帧和字幕文本,询问:“字号/字体/边距是否OK?确认OK后再进行全片渲染。”等待用户明确确认。
只有当用户在同一场对话中已经批准过使用相同字体配置对该视频进行全片渲染时,才可跳过此检查点。
bash
$FF -i input.mp4 \
  -vf "subtitles=input.zh-CN.srt:force_style='Fontname=PingFang SC\,Fontsize=12\,PrimaryColour=&H00FFFFFF\,OutlineColour=&H00000000\,BorderStyle=1\,Outline=2\,Shadow=1\,MarginL=20\,MarginR=20\,MarginV=40'" \
  -c:v libx264 -crf 18 -preset medium -pix_fmt yuv420p \
  -c:a copy output.mp4
force_style
中,需将每个逗号转义为
\,
(滤镜图解析器会将未转义的逗号视为链分隔符)。其他特殊字符无需处理。

Fontsize calibration — critical

字号校准——至关重要

libass scales its internal PlayRes up to the actual video resolution. The number you pass is not pixels in the output. As a starting calibration on a 544×960 vertical phone video,
Fontsize=22
rendered each Chinese character at ~55px wide and overflowed the frame, while
Fontsize=12
rendered at ~30–35px wide and fit cleanly with 15-char lines.
Rule of thumb: start at
Fontsize=12
, render, then always extract a frame and look:
bash
$FF -ss 30 -i output.mp4 -frames:v 1 /tmp/frame.png -y
libass会将内部PlayRes缩放至实际视频分辨率。你传入的字号数值并非输出视频中的像素大小。以544×960的竖屏手机视频为例,
Fontsize=22
会使每个中文字符宽约55px,超出画面;而
Fontsize=12
会使字符宽约30–35px,每行15个字符可完全容纳。
经验法则:从
Fontsize=12
开始渲染,然后务必提取帧查看效果:
bash
$FF -ss 30 -i output.mp4 -frames:v 1 /tmp/frame.png -y

then Read /tmp/frame.png to verify the longest-line cue fits

然后查看/tmp/frame.png,确认最长字幕行是否完全显示


Pick a timestamp that lands on the cue with the most characters per line — short lines won't expose overflow. Add `MarginL=20 MarginR=20` as a safety inset; never trust default left/right margins.

选择字幕行字符数最多的时间点——短字幕行无法暴露溢出问题。添加`MarginL=20 MarginR=20`作为安全边距;永远不要依赖默认的左右边距。

Style cheatsheet

样式速查表

Keys that matter (libass
force_style
):
  • Fontname=PingFang SC
    — macOS default CJK; alternates:
    Songti SC
    ,
    Heiti SC
    ,
    STHeiti
    ,
    Hiragino Sans GB
    .
  • Fontsize=12
    — start small, scale up only after frame check.
  • PrimaryColour=&H00FFFFFF
    — white text (BBGGRR + alpha).
  • OutlineColour=&H00000000
    — black outline.
  • BorderStyle=1
    — outline only (clean over varied backgrounds). Use
    BorderStyle=3
    for an opaque box behind text when the background is busy.
  • Outline=2
    — 2px outline thickness.
  • Shadow=1
    — subtle drop shadow.
  • MarginL=20 MarginR=20
    — keep text inside the frame.
  • MarginV=40
    — vertical distance from the bottom edge.
关键参数(libass
force_style
):
  • Fontname=PingFang SC
    — macOS默认中文字体;替代字体:
    Songti SC
    Heiti SC
    STHeiti
    Hiragino Sans GB
  • Fontsize=12
    — 从小字号开始,仅在帧检查后再调大。
  • PrimaryColour=&H00FFFFFF
    — 白色文本(格式为BBGGRR + 透明度)。
  • OutlineColour=&H00000000
    — 黑色描边。
  • BorderStyle=1
    — 仅描边(在多变背景下显示清晰)。当背景复杂时,使用
    BorderStyle=3
    在文本后方添加不透明背景框。
  • Outline=2
    — 描边厚度为2px。
  • Shadow=1
    — 轻微阴影。
  • MarginL=20 MarginR=20
    — 确保文本在画面内。
  • MarginV=40
    — 文本距底部边缘的垂直距离。

SRT line-length discipline for burn-in

烧录字幕的SRT行长度规范

Even with correct
Fontsize
, lines that are too long will wrap or overflow. Keep each on-screen line ≤ ~15 Chinese characters (~42 Latin chars). Use explicit
\n
line breaks inside the SRT block — do not rely on auto-wrapping. Two short lines beat one long one every time. (This is upstream discipline —
/wjs-translating-subtitles
should already cap cues at these limits.)
即使字号设置正确,过长的字幕行也会换行或溢出。每行显示的中文字符数≤约15个(约42个拉丁字符)。在SRT块中使用明确的
\n
换行——不要依赖自动换行。两行短字幕永远优于一行长字幕。(这属于上游规范——
/wjs-translating-subtitles
应已将字幕行限制在此范围内。)

Audio mixing — keep the original as a low-volume bed

音频混合——保留原音频作为低音量背景音

A pure dub-only track sounds dubbed (because it is). Mixing the original audio at low volume under the dub gives the "professional translation" feel — you still hear the speaker's breath, emphasis, and laughter, just under the new voice.
bash
$FF -i original.mp4 -i dub.mp4 \
  -filter_complex "[0:a]volume=0.18[orig];\
                   [1:a]volume=1.0[dub];\
                   [orig][dub]amix=inputs=2:duration=longest:normalize=0[a]" \
  -map 0:v -map "[a]" \
  -c:v copy -c:a aac -b:a 192k mixed.mp4
Reasonable starting volumes:
  • Original bed at
    0.15
    0.25
    (≈ −16 to −12 dB)
  • Dub at
    1.0
  • Use
    normalize=0
    so amix doesn't auto-attenuate when both are active.
To drop the original entirely:
--no-original-audio
(equivalent to
--bed-volume 0
).
纯配音轨道听起来很生硬(毕竟是后期配音)。将原音频以低音量混合在配音下方,可营造“专业翻译”的效果——你仍能听到原说话人的呼吸、重音和笑声,只是被新的配音覆盖。
bash
$FF -i original.mp4 -i dub.mp4 \
  -filter_complex "[0:a]volume=0.18[orig];\
                   [1:a]volume=1.0[dub];\
                   [orig][dub]amix=inputs=2:duration=longest:normalize=0[a]" \
  -map 0:v -map "[a]" \
  -c:v copy -c:a aac -b:a 192k mixed.mp4
合理的初始音量设置:
  • 原背景音:
    0.15
    0.25
    (≈ −16至−12 dB)
  • 配音:
    1.0
  • 使用
    normalize=0
    ,避免amix在两个音频同时播放时自动衰减音量。
若要完全移除原音频:使用
--no-original-audio
(等效于
--bed-volume 0
)。

Combining dub + burn-in + bed (the full job)

组合配音+烧录字幕+背景音(完整任务)

One ffmpeg call does all three — burn the target subtitle onto the video stream and mix the two audio tracks:
bash
$FF -i original.mp4 -i dub.mp4 \
  -filter_complex "[0:v]subtitles=input.zh-CN.srt:force_style='Fontname=PingFang SC\,Fontsize=12\,PrimaryColour=&H00FFFFFF\,OutlineColour=&H00000000\,BorderStyle=1\,Outline=2\,Shadow=1\,MarginL=20\,MarginR=20\,MarginV=40'[v];\
                   [0:a]volume=0.18[orig];[1:a]volume=1.0[dub];\
                   [orig][dub]amix=inputs=2:duration=longest:normalize=0[a]" \
  -map "[v]" -map "[a]" \
  -c:v libx264 -crf 18 -preset medium -pix_fmt yuv420p \
  -c:a aac -b:a 192k final.mp4
This is the "ship to social media" final cut.
render.py --video original.mp4 --dub dub.mp4 --srt input.zh-CN.srt
runs this exact pipeline.
一次ffmpeg调用即可完成三项操作——将目标字幕烧录到视频流,并混合两个音频轨道:
bash
$FF -i original.mp4 -i dub.mp4 \
  -filter_complex "[0:v]subtitles=input.zh-CN.srt:force_style='Fontname=PingFang SC\,Fontsize=12\,PrimaryColour=&H00FFFFFF\,OutlineColour=&H00000000\,BorderStyle=1\,Outline=2\,Shadow=1\,MarginL=20\,MarginR=20\,MarginV=40'[v];\
                   [0:a]volume=0.18[orig];[1:a]volume=1.0[dub];\
                   [orig][dub]amix=inputs=2:duration=longest:normalize=0[a]" \
  -map "[v]" -map "[a]" \
  -c:v libx264 -crf 18 -preset medium -pix_fmt yuv420p \
  -c:a aac -b:a 192k final.mp4
这就是可直接发布到社交媒体的最终剪辑版本。运行
render.py --video original.mp4 --dub dub.mp4 --srt input.zh-CN.srt
即可执行此完整流程。

Running
render.py

运行
render.py

bash
undefined
bash
undefined

Subtitles only (burn):

仅字幕(烧录):

python3 ~/.claude/skills/wjs-burning-subtitles/scripts/render.py
--video IN.mp4 --srt SUB.srt --out OUT.mp4
python3 ~/.claude/skills/wjs-burning-subtitles/scripts/render.py
--video IN.mp4 --srt SUB.srt --out OUT.mp4

Dub only (replace audio, no subs):

仅配音(替换音频,无字幕):

python3 ~/.claude/skills/wjs-burning-subtitles/scripts/render.py
--video IN.mp4 --dub IN_zh_dub.mp4 --out OUT.mp4
python3 ~/.claude/skills/wjs-burning-subtitles/scripts/render.py
--video IN.mp4 --dub IN_zh_dub.mp4 --out OUT.mp4

Full localized cut (burn + dub + original bed):

完整本地化剪辑(烧录字幕+配音+原背景音):

python3 ~/.claude/skills/wjs-burning-subtitles/scripts/render.py
--video IN.mp4 --srt IN.zh-CN.srt --dub IN_zh_dub.mp4 --out OUT.mp4
python3 ~/.claude/skills/wjs-burning-subtitles/scripts/render.py
--video IN.mp4 --srt IN.zh-CN.srt --dub IN_zh_dub.mp4 --out OUT.mp4

Soft-mux (no re-encode):

软封装(无需重新编码):

python3 ~/.claude/skills/wjs-burning-subtitles/scripts/render.py
--video IN.mp4 --srt SUB.srt --soft-mux --out OUT.mp4

See `render.py --help` for the full style/audio flag list (`--font`, `--fontsize`, `--color`, `--outline-color`, `--margin-v`, `--bed-volume`, `--no-original-audio`).
python3 ~/.claude/skills/wjs-burning-subtitles/scripts/render.py
--video IN.mp4 --srt SUB.srt --soft-mux --out OUT.mp4

查看`render.py --help`获取完整的样式/音频参数列表(`--font`、`--fontsize`、`--color`、`--outline-color`、`--margin-v`、`--bed-volume`、`--no-original-audio`)。

Output

输出文件

  • Burn mode:
    <source>_burned.mp4
    (re-encoded, libass-rendered subs)
  • Soft-mux mode:
    <source>_softsub.mp4
    (stream-copy,
    mov_text
    track)
  • Full cut:
    <source>_final.mp4
    (re-encoded video with burned subs + mixed audio)
  • 烧录模式:
    <source>_burned.mp4
    (重新编码,libass渲染字幕)
  • 软封装模式:
    <source>_softsub.mp4
    (流复制,含
    mov_text
    字幕轨道)
  • 完整剪辑模式:
    <source>_final.mp4
    (重新编码视频+烧录字幕+混合音频)

Anti-patterns

反模式

  • Promising burn-in without verifying libass. Check
    ffmpeg -filters | grep subtitles
    first; auto-fall back to evermeet static build if missing.
  • Committing a burn render without a frame check. Always extract a frame at the longest-line cue and Read it before kicking off the full render.
  • Bare commas inside
    force_style
    .
    The filter graph parser eats them. Escape every internal comma as
    \,
    .
  • Mixing libass burn-in with HyperFrames captions. Pick ONE caption system per output video. If you're using HTML/CSS captions in
    /wjs-overlaying-video
    , don't burn here too.
  • Using period milliseconds in the SRT. Whisper local writes
    .mmm
    ; libass tolerates it but other downstream tools choke. Normalize to
    ,mmm
    .
  • Defaulting to
    BorderStyle=3
    (opaque box).
    Use
    BorderStyle=1
    (outline only) unless the background is genuinely busy — the box looks heavy and dated.
  • 未确认libass可用就承诺烧录字幕。先检查
    ffmpeg -filters | grep subtitles
    ;若缺失,自动回退到evermeet静态构建包。
  • 未检查帧就进行全片烧录渲染。全片渲染前,务必提取最长字幕行对应的帧并查看。
  • force_style
    中使用未转义的逗号
    。滤镜图解析器会吃掉这些逗号。需将所有内部逗号转义为
    \,
  • 混合使用libass烧录字幕和HyperFrames字幕。每个输出视频只能选择一种字幕系统。若在
    /wjs-overlaying-video
    中使用HTML/CSS字幕,请勿在此处再进行烧录。
  • SRT中使用点号分隔毫秒。Whisper本地版会生成
    .mmm
    格式;libass可兼容,但其他下游工具会报错。需统一改为
    ,mmm
    格式。
  • 默认使用
    BorderStyle=3
    (不透明背景框)
    。除非背景确实复杂,否则使用
    BorderStyle=1
    (仅描边)——背景框看起来厚重且过时。

Upstream

上游依赖

  • /wjs-transcribing-audio
    +
    /wjs-translating-subtitles
    — produce the SRT input.
  • /wjs-dubbing-video
    — produces the
    *_<lang>_dub.mp4
    input for full-localized-cut mode. The dub-only file is technically a finished video; this skill is what mixes the original underneath and burns the subs to make it shippable.
  • /wjs-transcribing-audio
    +
    /wjs-translating-subtitles
    — 生成SRT输入文件。
  • /wjs-dubbing-video
    — 为完整本地化剪辑模式生成
    *_<lang>_dub.mp4
    输入文件。纯配音文件本质上是已完成的视频;本工具的作用是将原音频混合在下方并烧录字幕,使其成为可发布的版本。

Common pitfalls

常见陷阱

  • Fontsize that worked on one video looks tiny / huge on another. libass scales by PlayRes ratio, not pixels. Recalibrate per video resolution; don't trust a hardcoded value.
  • Margin defaults clip text on vertical phone videos. Always set
    MarginL=20 MarginR=20
    and
    MarginV=40
    (or higher) explicitly.
  • mov_text
    track shows up in QuickTime but not in some Android players.
    If the target audience is mobile-Chinese, soft-mux is unreliable; burn instead.
  • Background-bus busy / contrast issues. Increase
    Outline=2
    Outline=3
    , or switch to
    BorderStyle=3
    for a translucent box (
    BackColour=&H80000000
    for 50% black).
  • 在一个视频上适用的字号在另一个视频上显示过小/过大。libass按PlayRes比例缩放,而非像素。需根据每个视频的分辨率重新校准;不要依赖硬编码的数值。
  • 默认边距在竖屏手机视频上会裁剪文本。务必显式设置
    MarginL=20 MarginR=20
    MarginV=40
    (或更大值)。
  • mov_text
    轨道在QuickTime中显示正常,但在部分Android播放器中不显示
    。若目标受众是国内移动用户,软封装不可靠;应使用烧录字幕。
  • 背景复杂/对比度问题。将
    Outline=2
    改为
    Outline=3
    ,或切换到
    BorderStyle=3
    并添加半透明背景框(
    BackColour=&H80000000
    表示50%透明度的黑色)。