camera-3d-captions

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

3D camera captions

3D 摄像机字幕

The After Effects move (3D camera, depth of field, text on a path, Posterize Time 15, motion blur) rebuilt as a deterministic per-frame painter for HyperFrames. One clock paints plate, matte and every glyph. Nothing is a CSS animation.
将 After Effects 运镜(3D 摄像机、景深、路径上的文字、Posterize Time 15、运动模糊)重建为 HyperFrames 的确定性逐帧绘制器。由同一个时钟驱动 plate、matte 和每个字形的绘制。没有任何内容使用 CSS animation。

When to use / when not

何时使用 / 何时不使用

  • Use on a static-camera talking-head take (avatar or real), landscape, medium shot or wider, 5–20 s per piece. The words must perform in depth around the speaker.
  • Do not use for plain subtitles, a moving or handheld camera (the matte and the plate transform assume a locked shot), extreme close-ups (no room behind or around the head), or a subject a person-matting model can't separate.
  • 使用场景:固定机位的 talking-head 素材(虚拟形象或真人)、横屏、中景或更宽的景别,每段 5–20 秒。文字需要在说话者周围呈现纵深运动。
  • 不要用于:普通字幕、移动或手持摄像机画面(matte 和 plate 的变换假定镜头固定)、极近特写(头部后方或周围没有足够空间),以及人物分割模型无法分离的主体。

Requirements and side effects (complete list)

要求与副作用(完整清单)

  • Tools:
    • Node 22+ and the HyperFrames CLI pinned to the tested version,
      npx hyperframes@0.8.62
      (downloads from
      registry.npmjs.org
      ). Use that exact version for every command below.
    • ffmpeg
      on PATH.
    • Python 3 with
      fonttools
      ,
      brotli
      ,
      numpy
      ,
      pillow
      .
  • Matte:
    npx hyperframes@0.8.62 remove-background
    runs locally. On first run it downloads its person-segmentation model (~170 MB) to
    ~/.cache/hyperframes/
    .
  • Render time: compositions load GSAP 3.14.2 from
    cdn.jsdelivr.net
    at preview/render time.
  • Word clock: any word-level transcriber the user already runs locally. Onset accuracy matters; plain Whisper starts run late.
  • Fonts: user-supplied files. Confirm the licence allows embedding.
  • Hand-drawn style (optional): needs the
    p5-paint-animation
    skill installed and set up (its setup downloads pinned puppeteer, Chrome for Testing, p5 and p5.brush).
    build-sprites.py
    runs it headlessly; frames are written to that skill's
    out/
    and removed afterwards.
  • Nothing else: no credentials, no paid calls, nothing uploaded. Files are written only into the project (
    assets/
    ,
    renders/
    ).
  • 工具:
    • Node 22+,以及固定到已测试版本的 HyperFrames CLI:
      npx hyperframes@0.8.62
      (从
      registry.npmjs.org
      下载)。以下所有命令都必须使用这个精确版本。
    • PATH 中可用的
      ffmpeg
      。
    • Python 3,并安装
      fonttools
      、
      brotli
      、
      numpy
      、
      pillow
      。
  • Matte:
    npx hyperframes@0.8.62 remove-background
    在本地运行。首次运行会下载人物分割模型(约 170 MB)到
    ~/.cache/hyperframes/
    。
  • 渲染时:合成会在预览或渲染时从
    cdn.jsdelivr.net
    加载 GSAP 3.14.2。
  • Word clock:使用用户已在本地运行的任意逐词转录工具。起始时间准确度很重要;普通 Whisper 的启动时间偏晚。
  • 字体:由用户提供文件。需确认许可证允许嵌入。
  • 手绘风格(可选):需要安装并配置
    p5-paint-animation
    skill。其安装过程会下载固定版本的 puppeteer、Chrome for Testing、p5 和 p5.brush。
    build-sprites.py
    以 headless 模式运行;帧会写入该 skill 的
    out/
    ,并在完成后删除。
  • 除此之外:不需要凭据、付费调用,也不会上传任何内容。文件只会写入项目目录(
    assets/
    、
    renders/
    )。

Prep (once per take)

准备工作(每段素材执行一次)

  1. Portion and plate.
    bash scripts/prep-take.sh <take.mp4> <start_s> <dur_s> <project>/assets
    produces:
    • portion.mp4
      : the audio source.
    • plate-tall.mp4
      : reflect-padded 240 px each side, 480 px top, 240 px bottom, so whips, trucks and pull-backs never show an edge.
    • person.webm
      : the alpha matte, same frames.
    • The script then checks the plate and matte against the portion (size, frame rate, frame count) and fails on any mismatch.
  2. Word clock. Word timings for the portion, in seconds from its start.
  3. Fonts. Pick 2–3 roles (caption / hero / ring), then
    python3 scripts/font-metrics.py assets/metrics.js key=font.woff2[@wght=…,opsz=…] …
    .
    • A variable font must get its axes passed here AND pinned in CSS (
      font-variation-settings
      ,
      font-optical-sizing: none
      ). Otherwise the browser's opsz follows the size and words overlap.
  4. Silhouette. Read the head box and shoulder line off the
    person.webm
    alpha before placing anything.
  1. 片段与画布。
    bash scripts/prep-take.sh <take.mp4> <start_s> <dur_s> <project>/assets
    会生成:
    • portion.mp4
      :音频源。
    • plate-tall.mp4
      :左右各反射填充 240 px、顶部填充 480 px、底部填充 240 px,确保 whip、truck 和 pull-back 过程中不会露出边缘。
    • person.webm
      :alpha matte,帧范围与前两者相同。
    • 随后脚本会对照 portion 检查 plate 和 matte 的大小、帧率及帧数,任何不匹配都会导致失败。
  2. Word clock。 portion 的逐词时间,以片段起点后的秒数表示。
  3. 字体。 选择 2–3 种用途(caption / hero / ring),然后执行
    python3 scripts/font-metrics.py assets/metrics.js key=font.woff2[@wght=…,opsz=…] …
    。
    • 可变字体的字体轴必须同时在此处传入,并在 CSS 中固定(
      font-variation-settings
      、
      font-optical-sizing: none
      )。否则浏览器的
      opsz
      会随字号变化,导致文字重叠。
  4. 轮廓。 在放置任何元素之前,从
    person.webm
    的 alpha 中读取头部边界框和肩线。

Build

构建

  • Copy
    assets/kit/{cam3d.js,tables.js,finish.js}
    and
    metrics.js
    into the project's
    assets/
    .
  • references/worked-film.html
    is the complete reference film. It has two shots, a body wipe, both ring types, a fly-through, a finale and the grade; its media is not included.
  • references/recipes.md
    holds the depth-first groups, the wrapped ring, the staircase and hand-drawn words.
  • C3.init(metrics, {W, H})
    :
    • The lens keeps the tutorial's vertical field of view (F = 1326.1·H/1080), so
      tables.js
      applies unscaled at any width.
    • On a frame wider than 1440, add
      (W − 1440) / 2
      to
      wipe.x
      .
  • 将
    assets/kit/{cam3d.js,tables.js,finish.js}
    和
    metrics.js
    复制到项目的
    assets/
    。
  • references/worked-film.html
    是完整的参考成片,包含两个镜头、人物擦除转场、两种词环、穿行镜头、收尾和调色;其中不包含媒体文件。
  • references/recipes.md
    包含深度优先字幕组、环绕词环、阶梯式排布和手绘文字的配方。
  • C3.init(metrics, {W, H})
    :
    • 镜头保持教程中的垂直视场角(F = 1326.1·H/1080),因此
      tables.js
      可按未缩放比例应用于任意宽度。
    • 当画面宽度超过 1440 时,向
      wipe.x
      中添加
      (W − 1440) / 2
      。

Shot grammar

镜头语法

Pick 4–6 beats. Split each spoken clause into its own group.
BeatBuildRule
Whip-in punch-in
camA
table (19 f from 502 px low) then a
linear
creep
plate
S0
1.1–1.3; the frame never lands
Depth groups2–3 caption groups per clause at D 1250 / 1500 / 2200near = bigger with a bigger shadow; the creep and truck reveal the parallax
Hero behindhero word in the behind layer at the plate depth, masthead-style across the headthe head hides the middle letters; ≥ 60 % stays legible; the ascender is inside the frame
Parallax truck
swing
segment,
tx
420
the old layout fades over 3 steps; the next layout arrives by parallax
Body wipethe speaker's own matte,
brightness(0) blur()
, leading edge from
wipe.x
shot A clips left of the band, shot B right; pull-out on both sides
Wrapped ringhorizontal circle IN FRONT of the speaker (centre Z 2000), 11–16° from above, turningwords enter small at the side as spoken, swing round the front, settle; lower arc flips to read upright
Fly-throughphrases at D 1500 / 2000 / 2500, camera swings between themfocus follows the phrase
Staircase finalepush segment; caption words each deeper and smaller down a diagonalthe push drives the near words apart
选择 4–6 个节拍。将每段口语分句拆成独立字幕组。
节拍构建方式规则
Whip-in punch-in
camA
表(从低处 502 px 推进 19 帧),然后接一个
linear
creep
plate
使用
S0
1.1–1.3;画面永不落幅
深度字幕组每个分句设置 2–3 组字幕,D 为 1250 / 1500 / 2200近处文字更大、阴影更重;
creep
和
truck
展现视差
Hero behind主视觉词放在 behind layer,深度与 plate 相同,以 masthead 风格横跨头部头部遮住中间字母;至少 60% 保持可读;字母上伸部位于画面内
Parallax truck
swing
片段,
tx
为 420
旧布局在 3 个 step 内淡出;新布局通过视差运动进入
人物擦除转场使用说话者自身的 matte、
brightness(0) blur()
,前缘由
wipe.x
控制
镜头 A 在带状区域左侧被裁切,镜头 B 在右侧被裁切;两侧均做 pull-out
环绕词环位于说话者前方的水平圆环(中心 Z 2000),从上方观察有 11–16° 倾角,并持续旋转文字在对应语音开始时从侧面以小尺寸进入,绕到前景后停稳;下半弧翻转至正向可读
穿行镜头短语分别位于 D 1500 / 2000 / 2500,摄像机在它们之间摆动焦点跟随目标短语
阶梯式收尾push 片段;字幕词沿对角线逐级加深并缩小push 将近处文字相互拉开

Rules

规则

  • Motion:
    • Text is posterized to 15 fps (its own animation and the camera it sees). Plate and matte move every frame.
    • Motion blur is 7 ghosts over half a 15-fps step. Use
      shutter: 0.5
      on turning rings.
    • Captions lead the voice by 0.2 s, snapped to the 15-fps grid:
      fr(t) = 2·round((t − 0.2)·15)
      .
    • Entries rise by
      ENTRY
      : captions 30 px, heroes 110–260 px. Ring words ease their last 30–44 px along the path. Use
      RING_ENTRY
      's overshoot only when no neighbour is seated yet.
  • Depth:
    • Every group gets its own D. Plate 3000, captions 1250–2400, wrapped ring ≈ 1100–2700.
    • Depth shadow
      [6, 16, 0.5, 1500]
      scales as 1500/(D − cz).
    • DOF σ = 6.7·|D − focus|/D px at 1080 tall. Flat captions cap at 1.2 px; ring glyphs take the full blur.
  • Sizes:
    size
    /
    rise
    are on-screen px at the landing camera (
    tland
    ). Sprites are rendered at their on-screen size.
  • Spacing:
    • Ring text always goes through
      ringLine
      (on-screen advance; type: track 1.10, gap 26–30; hand-drawn: track 1.0, gap 28).
    • A turning ring is re-laid every pose.
  • Occlusion:
    • Rings draw in front of the speaker.
    • Split a word into behind/front instances (
      side
      ,
      zSplit
      ) only when it is MEANT to pass behind the head.
  • Legibility:
    • Captions go over the dark side of the frame.
    • Text crossing skin or light clothing gets a dark halo.
    • No trailing periods on screen.
  • Grade (optional):
    finish.js
    supplies curves, saturation 1.12 for skin, and grain re-rolled per frame. Put the opaque scene (plate + behind text + matte) in a filtered wrapper. Give front text the same grade with grain composited atop, so a word split behind/front stays one colour (see
    references/worked-film.html
    ).
  • 运动:
    • 文字以 15 fps 做帧率量化(文字自身的动画及其所看到的摄像机运动均如此)。plate 和 matte 则每帧运动。
    • 运动模糊由 7 个残影覆盖 15 fps 步长的一半构成。旋转词环使用
      shutter: 0.5
      。
    • 字幕比语音提前 0.2 s,并吸附到 15 fps 网格:
      fr(t) = 2·round((t − 0.2)·15)
      。
    • 入场位移由
      ENTRY
      控制:字幕为 30 px,主视觉词为 110–260 px。词环文字在路径最后 30–44 px 的范围内缓动。仅当相邻词尚未就位时,才使用
      RING_ENTRY
      的 overshoot。
  • 深度:
    • 每组使用独立的 D。plate 为 3000,字幕为 1250–2400,环绕词环约为 1100–2700。
    • 深度阴影
      [6, 16, 0.5, 1500]
      按 1500/(D − cz) 缩放。
    • 在高度为 1080 时,DOF σ = 6.7·|D − focus|/D px。平面字幕的模糊上限为 1.2 px;词环字形使用完整模糊量。
  • 尺寸:
    size
    /
    rise
    表示落地摄像机(
    tland
    )下的屏幕像素。sprite 按其屏幕尺寸进行渲染。
  • 间距:
    • 词环文字始终通过
      ringLine
      排版(屏幕前进宽度;印刷体:track 1.10、gap 26–30;手绘:track 1.0、gap 28)。
    • 旋转词环在每个姿态下重新排版。
  • 遮挡:
    • 词环绘制在说话者前方。
    • 仅当某个词确实需要从头部后方穿过时,才使用
      side
      、
      zSplit
      将其拆分为 behind/front 实例。
  • 可读性:
    • 字幕放在画面较暗的一侧。
    • 文字穿过皮肤或浅色服装时添加深色 halo。
    • 屏幕字幕末尾不显示句号。
  • 调色(可选):
    finish.js
    提供曲线、1.12 的肤色饱和度,以及逐帧重新生成的 grain。将不透明场景(plate + behind text + matte)放入应用滤镜的 wrapper。前景文字使用相同的调色,并将 grain 叠加在上层,使拆分为 behind/front 的词保持同一种颜色(参见
    references/worked-film.html
    )。

Styles

风格

  • Editorial: a condensed display serif, italic for heroes; a text serif for captions; cream plus one accent.
  • Bold: heavy grotesk heroes, light captions, one saturated accent (the worked film).
  • Hand-drawn: p5.brush write-on sprites from
    scripts/hand/build-sprites.py
    :
    • Each word writes itself on from its onset.
    • Rings place words whole, rotated to the tangent and scaled by depth, on a bigger, flatter ring.
    • Recipe in
      references/recipes.md
      .
  • 编辑排版:紧凑型标题衬线字体,主视觉词使用斜体;字幕使用正文字体衬线;奶油色搭配一种强调色。
  • 粗体风格:主视觉词使用粗重 grotesk 字体,字幕使用轻字重,并搭配一种高饱和度强调色(参考成片采用此风格)。
  • 手绘:使用
    scripts/hand/build-sprites.py
    生成 p5.brush 书写显现 sprite:
    • 每个词从其起始时刻开始逐笔写出。
    • 词环以完整单词进行放置,按切线方向旋转,并根据深度缩放;词环更大、更扁。
    • 配方见
      references/recipes.md
      。

Verify

验证

  1. npx hyperframes@0.8.62 check . --no-contrast
    . The ghost layers trip the WCAG contrast check; the whip and wipe seams are overlapping by design.
  2. In the preview page, paste
    scripts/frame-bounds.js
    and run
    __frameBounds(0, dur)
    .
    • It must return
      []
      : no group outside the frame for 5+ steps while visible.
  3. Paste
    scripts/glyph-gaps.js
    and run
    __glyphGaps(t)
    at rest.
    • No cross-word touches.
    • Flat words measure as one box (descenders flag false positives). An italic f's overhang reads negative. Words on opposite sides of the wipe band both show.
  4. Word gaps: the rendered gap between words ≈ one space advance.
  5. Snapshot every beat, render with
    npx hyperframes@0.8.62 render . --crf 12
    , and check full-resolution frames per beat.
  1. npx hyperframes@0.8.62 check . --no-contrast
    。残影图层会导致 WCAG 对比度检查失败;whip 和 wipe 的接缝重叠属于预期设计。
  2. 在预览页面中粘贴
    scripts/frame-bounds.js
    并运行
    __frameBounds(0, dur)
    。
    • 必须返回
      []
      :可见期间连续 5 个以上 step 都不能有字幕组超出画面。
  3. 粘贴
    scripts/glyph-gaps.js
    ,并在静止状态下运行
    __glyphGaps(t)
    。
    • 不得出现跨词接触。
    • 平面词应测量为一个 box(descender 可能产生误报)。斜体 f 的悬伸会显示为负值。位于 wipe 带两侧的词都应显示。
  4. 词间距:渲染后的词间空隙约等于一个空格的前进宽度。
  5. 为每个节拍创建快照,执行
    npx hyperframes@0.8.62 render . --crf 12
    ,并逐个检查节拍的全分辨率画面。

Limits

限制

  • Matte edges flicker on fine hair. The mirrored pad shows briefly during a whip.
  • A speaker who fills the frame leaves little room behind: go masthead-high, or tuck behind a shoulder.
  • snapshot --at
    shares one page, so grain does not re-roll between captures. Judge grain on a render.
  • Rendering with the grade and many ghost layers costs about 2× a plain render.
  • 细发丝处的 matte 边缘可能闪烁。whip 过程中会短暂露出镜像填充区域。
  • 如果说话者占满画面,头部后方几乎没有空间:可将文字提高到 masthead 高度,或藏到肩膀后方。
  • snapshot --at
    共用同一个页面,因此多次截图之间 grain 不会重新随机生成。应以实际 render 的结果判断 grain。
  • 同时应用调色和大量残影图层时,渲染耗时约为普通渲染的 2 倍。