Home / All Skills / Content & Media Creation / camera-3d-captions
Content & Media Creation · heygen-com/hyperframes-community-skills

camera-3d-captions

> Captions living in 3D space around a talking head: a camera flies between the speaker and the words (whip-in, parallax truck, push), caption groups sit at different depths so camera moves pull them apart, hero words hide BEHIND the speaker through an alpha matte, a ring of words wraps round the speaker and turns in front of them, focus racks between depths, and text steps at 15 fps with ghost motion blur. Works in any type style (editorial serif, bold sans) or hand-drawn (p5.brush write-on). Trigger on: "3D text", "3D camera captions", "text around / behind the speaker", "camera moves through the text", "depth captions on my avatar video". Covers prep (portion, padded plate, matte, word clock, font metrics), the shot grammar, depth rules, QC, and limits.

L4 High-Privilege · Graded auth & full auditV1 Commit-PinnedExecute CommandNetwork AccessWrite FileDual-pass Audit
Author heygen-comGitHub heygen-com/hyperframes-community-skills ↗Stars 156License Apache-2.0(仓库根 LICENSE)Commit ba7a0bb6d3
Control levels describe skill capabilities and suggested control measures; they do not represent quality or safety certifications, nor do they dictate usability. Usage decisions rest entirely with users and administrators. Different agent hosts apply varying constraints; do not assume hosts provide automatic protection. Third-party skill copyrights belong to their original authors. Use at your own risk.

1How It Works & Mechanism

The implementation path is 'rebuilding AE techniques into a frame-by-frame deterministic renderer': a single analytical clock renders the plate, matte, and each glyph simultaneously, with the entire composition avoiding CSS animations (so that the renderer supports arbitrary seeking).

skills/camera-3d-captions/SKILL.md
One clock paints plate, matte and every glyph. Nothing is a CSS animation.
What is happening here: This rule dictates all subsequent constraints (no repeat:-1, no CSS transitions, time must be analytically evaluable).

The camera is not true 3D, but an analytical projection: each shot defines cz (dolly along the axis) and px/py (screen shifts). All layers exist in 'reference-screen coordinates under a reference camera', projected via screen = C + (ref − C)·D0/(D0 − cz) + (px,py); text steps at 15 fps and samples the camera synchronously, overlaid with 7-pass ghost motion blur.

skills/camera-3d-captions/assets/kit/cam3d.js
as a deterministic per-frame painter for HyperFrames. One analytic clock: CAM3D.render(t) writes every style.
Immediate model description in the same file: 'Model: each shot has a camera {cz (dolly toward the scene), px/py (pan/tilt as screen shift)}; every layer lives in "ref-screen" coordinates'. In the implementation, project()/unproject() are the forward and inverse solutions of this equation.

The 'AE feel' does not rely on hardware capabilities, but on three hardcoded timing disciplines: text is POSTERIZED to 15 fps (sampled together with the camera it observes), 7-pass ghost motion blur is applied, and the plate and matte update every frame.

skills/camera-3d-captions/assets/kit/cam3d.js
Text is posterized (camera AND own animation sampled at 15 fps) with 7-ghost motion blur; the plate/matte move every frame with a directional Gaussian from camera speed.
What is happening here: Translating 'Posterize Time 15 + Motion Blur' into constants POST = 15, K = 7 (see in the same file: var W = 1440, H = 1080, CX = 720, CY = 540, FPS = 30, POST = 15, K = 7;).

The timeline itself is NOT locked to a rigid template: the storyboard is dynamically split into 4–6 beats by the model based on spoken lines, splitting into a group per clause; what is locked is the render contract—15 fps grid, captions leading voice by 0.2 s, ENTRY displacement, and depth shadow/DOF formulas.

skills/camera-3d-captions/SKILL.md
Pick 4–6 beats. Split each spoken clause into its own group.
Lead time formula from the same file: 'Captions lead the voice by 0.2 s, snapped to the 15-fps grid: `fr(t) = 2·round((t − 0.2)·15)`.'—meaning shot tables (camA/camB/wipe) are empirical data lookups, while sequence ordering is driven by content.

The asset pipeline uses a one-time prep: cuts out the portion (audio source), plate-tall with reflected edge padding (prevents edge exposure during whip/push-pull camera moves), and the synchronized alpha matte (person.webm), then verifies all three item-by-item across dimensions/framerate/frame count, immediately exiting with code 1 upon mismatch—this is where 'timeline lock' actually takes effect.

skills/camera-3d-captions/scripts/prep-take.sh
[ "$tn" -eq "$pn" ] && [ "$mn" -eq "$pn" ] || fail "frame counts differ: portion $pn, plate $tn, matte $mn"
Parameters and default padding in usage comments of the same file: prep-take.sh <take.mp4> <start_s> <dur_s> <project/assets> [padTop=480] [padBottom=240] [padSide=240]; matte runs via npx -y "$HF" remove-background ... --quality best (local inference).

Font metrics are externalized into generatable data tables: font-metrics.py uses fontTools to extract each glyph's advance (em) and line-height 1 baseline, instantiating variable fonts at specified axes first; the engine then arranges each glyph individually (disabling CSS kerning), requiring variable axes to be pinned in CSS as well to prevent width drift from browser opsz scaling.

skills/camera-3d-captions/scripts/font-metrics.py
Writes `window.CAM3D_METRICS = {adv: {key: {char: em}}, base: {key: em}}`.
Corresponding discipline in SKILL.md: '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.'

Camera motion curves are not fitted, but frame-by-frame measured lookup tables extracted from reference tutorials: tables.js stores camA (19-frame whip-in + crawl), camB (pull out/stabilize/push), wipe leading edge x, and remaining entry ratio, indexed at 30 fps frames and scaled by H/1080.

skills/camera-3d-captions/assets/kit/tables.js
/* tables.js — measured motion tables (skill camera-3d-captions). Frame-indexed at 30 fps; px values are for a 1080-tall frame (scale by H/1080);
Width adaptation note in the same file: 'on a wider frame add (W - 1440) / 2 to wipe.x so the band still crosses mid-frame at ref f40'—consistent with the Build section of SKILL.md.

Hand-drawn style is reused across skills: leverages the installed p5-paint-animation engine to render each word into write-on sprite sheets (build-sprites.py invokes render-anim.mjs from that engine via subprocess, reading the frame directory and deleting it afterward); hence this style depends on the setup of another skill.

skills/camera-3d-captions/SKILL.md
**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.
Implementation seen in scripts/hand/build-sprites.py: subprocess.run(['node','render-anim.mjs', sk, out, ...], cwd=engine), with the engine directory specified via --engine or the P5_ENGINE environment variable.

2Core Capabilities

01Camera maneuvers between the speaker and typography (whip-in, parallax truck, push), pulling apart text layers separated across depth planes
02Hero words hide behind the speaker via alpha matte (middle letters obstructed by head while preserving ≥60% legibility)
03Orbiting text ring: words rotate along a horizontal ring in front of the speaker (anchored on the ring, rotated tangentially, scaled by depth), with the bottom arc flipping upright automatically
04Depth constraints: each group gets an independent D value, shadows scale with depth distance, and blur radius σ scales linearly with |D − focus| (capped at 1.2 px for flat text, full blur applied to ring glyphs)
05Body wipe transition: uses the speaker's own matte turned black as a wiping bar sweeping across (Shot A captures left of bar, Shot B captures right), pulling out simultaneously on both flanks
06Hand-drawn write-on style (strokes dynamically drawn via p5.brush; full words positioned along the ring, rotated tangentially, and scaled by depth)
07Triple QC suite: in-frame boundary overflow detection (executed inside the preview page), glyph gap collision detection, and prep-stage dimensions/framerate/frame count alignment checks
08Render output: beat-by-beat frame inspection check + `render --crf 12` final export; optional post-processing finish (curves/SAT 1.12/per-frame randomized film grain)

3External Dependencies

KindDependency / Endpoint
cliHyperFrames CLI(钉死 0.8.62,经 npx 从 npm 取)
cliffmpeg / ffprobe(切分、反射补边、探帧率与帧数、最后编码)
packagePython3 + fonttools/brotli/numpy/pillow(字体度量与手绘精灵图)
network抠像模型首次运行下载(人物分割权重 ~170 MB,本地推理)
networkGSAP 3.14.2(合成在预览/渲染时由页面加载;参考片 HTML 也引用同一 CDN)
package跨 skill 依赖:p5-paint-animation 的渲染引擎(仅手绘风格需要;用 render-anim.mjs 生成精灵图)
cli素材来源:用户自备 take 视频、自备字体(需自行确认嵌入许可)、以及用户自己本地跑的词级转录器

4Recommended Governance & Evidence

Suggested Controls for Safe Operation
For Agent Runtime
Graded/per-use authorization, sandbox recommended
For Administrators
Approval workflow, rollback support, full audit
Evidence & Triggers (File Paths & Excerpts)
  • scripts/: Executes local script or command: bash scripts/prep-take.sh <take> <start> <dur> <assets> (ffmpeg/ffprobe subprocesses;
  • 网络端点: Declares external network access: registry.npmjs.org: fetches hyperframes@0.8.62 via npx per execution (pinned version; does not follow latest)
  • 本地工作目录: File read/write and artifact output: inside project assets/: portion.mp4, plate-tall.mp4, person.webm, metrics.js, hand/*.png and
Audit Notes: Entire codebase touches no credentials: no env/keychain/cookie reads, no API key assembly, no external generation APIs; relies strictly on user-provided footage and fonts, with SKILL.md explicitly stating 'no credentials, no paid calls, nothing uploaded'. Rated Yellow (instead of Blue/Green) due to predictable but non-free runtime network access: ① Every command fetches hyperframes@0.8.62 via npx from registry.npmjs.org (pinned version, deterministic); ② First-time matting downloads ~170 MB human segmentation weights to ~/.cache/hyperframes/ (one-time supply chain surface); ③ Composition page loads gsap@3.14.2 from cdn.jsdelivr.net during preview/render, making rendering non-fully offline. Shares identical patterns with the yellow-rated embedded-captions (first-run third-party packages + local model weights + jsdelivr gsap). No TLS downgrades, anti-scraping bypasses, or arbitrary code execution surfaces detected (subprocesses only invoke fixed binaries and bundled scripts), thus not escalating to Orange. Hand-drawn styling invokes another skill's engine and writes temp frames to its out/ directory—following the scope criteria of 'evaluation target = skill's own directory', this does not elevate the rating, and risks have been documented.

5Second-Pass Verification

  • [ok] skill 自身是否读凭据(黄/橙分界的关键) — 全目录 process.env / os.environ 只命中 build-sprites.py 的 P5_ENGINE(p5 引擎目录路径);无 API key、token、cookie、keychain、.env 读取。因此按范式的橙档第一条不成立,落黄。
  • [ok] 「外部资源」逐条回查调用点 — hyperframes@0.8.62 在 SKILL.md 与 prep-take.sh(HF="hyperframes@0.8.62")两处一致;ffmpeg/ffprobe 在 prep-take.sh 的 ffmpeg/probe() 内可验;fontTools 在 font-metrics.py import 段;GSAP CDN 在 worked-film.html 与 SKILL.md 的 Render time 条;p5 引擎调用在 build-sprites.py 的 subprocess.run(cwd=engine)。
  • [ok] prep 阶段的三者对齐自检是否真会失败退出(不是装饰性文字) — prep-take.sh 定义 fail(){ echo ... >&2; exit 1; },并对 plate 尺寸、matte 尺寸、三者的帧率与帧数各写了一条比较分支,任一不符即 exit 1;成功路径打印 'prep-take: plate and matte aligned with the portion ($pn frames @ $pr)'。
  • [ok] 手绘风格是否真的复用 p5-paint-animation 引擎,且临时帧会被清理 — build-sprites.py 用 subprocess.run(['node','render-anim.mjs', ...], cwd=engine) 渲染临时 sketch(写在 tempfile 目录,落在自己的 temp 而非引擎内),读引擎 out/_frames_<name>.anim/ 的 PNG 后调用 shutil.rmtree(fdir, ignore_errors=True);与 SKILL.md『frames are written to that skill's out/ and removed afterwards』一致。
  • [discrepancy] worked-film.html 能否按 SKILL 的 Build 步骤原样打开(参考片的可运行性声明) — SKILL.md 只说 'its media is not included',但参考片的 <head>/<body> 还引用了 assets/o2o-data.js(注释写 'window.O2O here = assets/kit/tables.js')与 assets/Inter-Variable.woff2、assets/InterTight-900.woff2 等字体文件;o2o-data.js 与这两个 woff2 均不在包内(全量清单已确认)。按 SKILL 的『Copy assets/kit/{cam3d.js,tables.js,finish.js} and metrics.js into the project's assets/』操作仍缺 o2o-data.js,参考片不能原样运行——读者须自行把 tables.js 改名/桥接成 window.O2O 并自带字体。该出入对 pin commit 有效。
  • [ok] 15 fps 网格与 0.2 s 提前量是否真在实现里(而不是只写在文档) — cam3d.js 顶部 var W = 1440, H = 1080, CX = 720, CY = 540, FPS = 30, POST = 15, K = 7;,并有 post(t) = Math.floor(t * POST + 1e-4) / POST;SKILL.md 给出 fr(t) = 2·round((t − 0.2)·15)(×2 即 30 fps 渲染下的 15 fps 映射),两处自洽。
  • [unlocatable] 『带 grade 与多重影渲染约 2× 成本』的量化声明 — SKILL.md 的 Limits 段写 'Rendering with the grade and many ghost layers costs about 2× a plain render.',包内没有基准脚本或计时数据可复现该倍数;本次为只读侦查且不得运行渲染,故仅作为文档声明记录,不作为已核事实使用。

6Verdict & Scenarios

  • Deconstructs AE visual aesthetics into deterministic, reproducible mechanics: analytical camera + 15 fps posterization + 7-pass ghosting + empirical motion tables, avoiding manual keyframing
  • Asset pipeline integrates strict consistency gates: plate and matte must match portion across all parameters, failing fast with exit 1 to prevent overall composition misalignment
  • Typography bypasses native browser flow: advance metrics and baselines are externalized via font-metrics.py, allowing the engine to place each glyph manually with pinned CSS variable font axes
  • QC measures directly against real browser DOM and canvas metrics (out-of-bound words, cross-word collisions) rather than rough estimates
  • Honest declaration of prerequisites and technical trade-offs (Node 22+, four Python packages, font licensing, hairline matte jitter, screen-filling speaker limitations)
  • Best for: Best suited for: locked-off speaker footage (avatars or real actors) requiring 5–20 s sequences of '3D typography acting around the subject'—featuring camera flights between speaker and text, depth-separated parallax captions, hero words masked behind the head, orbiting text rings, rack focus between layers, and 15 fps stepped text with ghosting. Also ideal for porting AE Posterize Time/3D camera/DOF workflows into HyperFrames deterministic pipelines with strict QC for boundary overflow and glyph collision.
    Not for: Not suitable for: ① Standard captioning (should use tools like embedded-captions); ② Moving/handheld shots or extreme close-ups (matte and plate warps assume locked camera without head-bleed margins); ③ Footage where segmentation models fail to isolate the subject; ④ Real-time or interactive web interfaces rather than offline video rendering; ⑤ Fully offline environments without npm; ⑥ Users unwilling to accommodate an initial ~170 MB model download and CDN dependencies during rendering.
    Install Agent-Ready
    ① Verified Mirror 2026-09-28
    Option A · Direct DownloadDownload camera-3d-captions.tar.gz
    sha256: 70cf48e51fa89f4e…
    Option B · Copy Guide for Agent
    install.json
    Agents automatically download and verify mirrors from this JSON guide.
    ② Upstream GitHub
    GitHub Repo ↗
    Pinned to commit ba7a0bb6d3.