@hyperframes/engine
"@hyperframes/engine"
engine 包提供了底层视频捕获管道:它在无头 Chrome 中加载 HTML 页面,独立地跳转到每一帧,并使用 Chrome 的 HeadlessExperimental.beginFrame API 捕获像素缓冲区。这是使 Hyperframes 渲染具有确定性的核心层。
npm install @hyperframes/engine
何时使用
⚠️ Warning
大多数用户不应直接使用 engine。 请改用 CLI(
npx hyperframes render)或 producer 包 — 它们会为你处理运行时注入、音频混音和编码。
在以下场景使用 @hyperframes/engine:
- 构建具有完全帧捕获控制的自定义渲染管道
- 将 Hyperframes 捕获集成到现有的视频处理系统中
- 捕获单个帧(例如用于缩略图或精灵图)而不编码为视频
- 实现自定义编码后端(非 FFmpeg)
如果需要以下功能,请使用其他包:
工作原理
engine 实现了一个与屏幕录制根本不同的跳转并捕获循环:
启动无头 Chrome
engine 启动 chrome-headless-shell,一个为通过 Chrome DevTools Protocol (CDP) 进行编程控制而优化的最小化无头 Chrome 二进制文件。
加载合成
你的 HTML 合成被加载到浏览器页面中。注入 Hyperframes 运行时来管理时间轴跳转。
逐帧跳转
对于视频中的每一帧(例如 30fps 的 30 秒视频有 900 帧),engine 调用 renderSeek(time) 将合成推进到精确的时间戳。不涉及时钟时间 — 每一帧都是独立定位的。
通过 BeginFrame 捕获
Chrome 的 HeadlessExperimental.beginFrame API 将合成器输出捕获为像素缓冲区。这会产生像素完美的帧,没有任何屏幕录制伪影。
传递帧数据
捕获的帧缓冲区被传递给消费者 — 通常是 FFmpeg(通过 producer)编码为 MP4,但你也可以提供自己的消费者。
这种方法保证了确定性渲染:相同的 HTML 始终产生相同的视频,无论系统负载或时序如何。
配置
import { resolveConfig, DEFAULT_CONFIG } from '@hyperframes/engine';
import type { EngineConfig } from '@hyperframes/engine';
// 使用默认值
const config = DEFAULT_CONFIG;
// 或使用覆盖项解析
const config = resolveConfig({
// ... 自定义选项
});
质量预设
| 预设 | 使用场景 | 速度 |
|---|---|---|
draft | 开发期间的快速迭代 | 最快 |
standard | 生产渲染,质量与速度的良好平衡 | 中等 |
high | 最终交付,最高质量 | 最慢 |
FPS 选项
| FPS | 使用场景 |
|---|---|
24 | 电影感,较小的文件大小 |
30 | 标准网页视频,良好的平衡 |
60 | 流畅的运动,UI 动画,屏幕录制 |
编程方式使用
engine 使用基于会话的 API 进行帧捕获:
import {
createCaptureSession,
initializeSession,
captureFrame,
captureFrameToBuffer,
getCompositionDuration,
closeCaptureSession,
} from '@hyperframes/engine';
// 1. 创建捕获会话
const session = await createCaptureSession({ fps: { num: 30, den: 1 }, width: 1920, height: 1080 });
// 2. 用合成初始化
await initializeSession(session, './my-video/index.html');
// 3. 获取总时长
const duration = getCompositionDuration(session);
// 4. 捕获帧
const totalFrames = Math.ceil(duration * 30);
for (let i = 0; i < totalFrames; i++) {
// 捕获到磁盘
const result = await captureFrame(session, i);
// result.path, result.captureTimeMs
// 或捕获到缓冲区(内存中)
const bufResult = await captureFrameToBuffer(session, i);
// bufResult.buffer, bufResult.captureTimeMs
}
// 5. 清理
await closeCaptureSession(session);
浏览器管理
import {
acquireBrowser,
releaseBrowser,
resolveHeadlessShellPath,
buildChromeArgs,
} from '@hyperframes/engine';
// 获取浏览器实例(创建或从池中复用)
const browser = await acquireBrowser();
// 获取 Chrome 二进制路径
const chromePath = await resolveHeadlessShellPath();
// 使用完毕后释放
await releaseBrowser(browser);
编码
engine 包含 FFmpeg 编码工具,支持 MP4 (h264) 和 WebM (VP9 带 alpha):
import {
encodeFramesFromDir,
muxVideoWithAudio,
applyFaststart,
detectGpuEncoder,
getEncoderPreset,
ENCODER_PRESETS,
} from '@hyperframes/engine';
// 获取格式感知的编码器设置
const mp4Preset = getEncoderPreset('standard', 'mp4');
// { codec: "h264", pixelFormat: "yuv420p", preset: "medium", quality: 23 }
const webmPreset = getEncoderPreset('standard', 'webm');
// { codec: "vp9", pixelFormat: "yuva420p", preset: "good", quality: 23 }
// 将捕获的帧编码为视频
await encodeFramesFromDir(framesDir, 'frame_%06d.png', outputPath, {
fps: { num: 30, den: 1 },
...webmPreset,
});
// 将视频与音频混音(WebM 使用 Opus,MP4 使用 AAC)
await muxVideoWithAudio(videoPath, audioPath, outputPath);
// 应用 MP4 faststart 用于流式传输(WebM 不适用)
await applyFaststart(inputPath, outputPath);
// 检测 GPU 编码支持
const gpu = await detectGpuEncoder();
// gpu: "nvenc" | "videotoolbox" | "vaapi" | "qsv" | "amf" | null
WebM VP9 Alpha
当编码透明度时,使用 format: "webm" 配合 getEncoderPreset()。这会配置:
- VP9 编解码器 (
libvpx-vp9) 配合支持 alpha 的yuva420p像素格式 -auto-alt-ref 0和alpha_mode=1元数据用于正确的 alpha 编码-row-mt 1用于多线程 VP9 编码- Opus 音频 在混流步骤中(而非 MP4 的 AAC)
流式编码器
无需将帧写入磁盘即可进行内存高效的编码:
import { spawnStreamingEncoder } from '@hyperframes/engine';
const encoder = await spawnStreamingEncoder({
outputPath: './output.mp4',
fps: { num: 30, den: 1 },
width: 1920,
height: 1080,
});
// 直接将帧送入编码器
encoder.writeFrame(frameBuffer);
// ...
const result = await encoder.finalize();
视频帧提取
从源视频文件中提取帧以注入浏览器:
import {
parseVideoElements,
extractAllVideoFrames,
getFrameAtTime,
createFrameLookupTable,
FrameLookupTable,
} from '@hyperframes/engine';
// 从 HTML 解析视频元素
const videos = parseVideoElements(html);
// 从视频中提取所有帧
const frames = await extractAllVideoFrames(videoPath, { fps: 30 });
// 创建用于快速帧访问的查找表
const lookup = createFrameLookupTable(frames);
const frame = lookup.getFrameAtTime(5.0);
音频处理
import { parseAudioElements, processCompositionAudio } from '@hyperframes/engine';
// 从 HTML 解析音频元素
const audioElements = parseAudioElements(html);
// 处理并混音所有音频轨道
const mixResult = await processCompositionAudio({ audioElements, duration, fps });
并行渲染
import {
calculateOptimalWorkers,
distributeFrames,
executeParallelCapture,
getSystemResources,
} from '@hyperframes/engine';
// 检查系统资源
const resources = getSystemResources();
// 计算最优 worker 数量
const workers = calculateOptimalWorkers(totalFrames);
// 在 worker 之间分配帧
const tasks = distributeFrames(totalFrames, workers);
// 执行并行捕获
const results = await executeParallelCapture(tasks);
文件服务器
通过 HTTP 为浏览器提供合成文件:
import { createFileServer } from '@hyperframes/engine';
const server = await createFileServer({ root: './my-video', port: 0 });
// server.url, server.port
// ... 使用 server.url 作为合成 URL
await server.close();
HDR API
engine 导出两层 HDR 支持:色彩空间工具(用于分类源并配置 FFmpeg 编码器)和 WebGPU 回读运行时(用于将 CSS 动画 DOM 直接捕获为 HDR)。
要进行端到端的 HDR 渲染(将 HDR 视频和图像源合成为 HDR10 MP4),请使用 producer 或带有 HDR 自动检测 / --hdr / --sdr 的 CLI 渲染管道 — 参见 HDR 渲染。以下 API 用于自定义集成。
色彩空间工具
import {
isHdrColorSpace,
detectTransfer,
analyzeCompositionHdr,
getHdrEncoderColorParams,
DEFAULT_HDR10_MASTERING,
} from '@hyperframes/engine';
import type { HdrTransfer, HdrEncoderColorParams, HdrMasteringMetadata } from '@hyperframes/engine';
// 从 ffprobe 色彩空间分类单个源
isHdrColorSpace(colorSpace); // boolean — BT.2020 / PQ / HLG 时为 true
detectTransfer(colorSpace); // 'pq' | 'hlg'(先检查 isHdrColorSpace)
// 在多个源中选择主要的传输特性
analyzeCompositionHdr([cs1, cs2]); // { hasHdr, dominantTransfer: 'pq' | 'hlg' | null }
// 为 x265 构建 FFmpeg 色彩参数 + HDR10 静态元数据
const params = getHdrEncoderColorParams('pq');
// {
// colorPrimaries: 'bt2020',
// colorTrc: 'smpte2084',
// colorspace: 'bt2020nc',
// pixelFormat: 'yuv420p10le',
// x265ColorParams: 'colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc:master-display=...:max-cll=1000,400',
// mastering: { masterDisplay: '...', maxCll: '1000,400' },
// }
getHdrEncoderColorParams 始终包含颜色标签和 HDR10 静态元数据(母版显示 + 内容光照级别)。没有这些元数据,下游播放器会将文件视为 SDR BT.2020 并进行错误的色调映射。如果你有测量过的逐内容值,请传入自定义 HdrMasteringMetadata;否则保守的 DEFAULT_HDR10_MASTERING 默认值与大多数 HDR10 调色套件标记内容的方式一致。
WebGPU HDR DOM 捕获
要将 CSS 动画 DOM 直接捕获为 HDR(不涉及 FFmpeg 源),engine 提供了单独的 WebGPU 管道:
import {
launchHdrBrowser,
buildHdrChromeArgs,
initHdrReadback,
uploadAndReadbackHdrFrame,
float16ToPqRgb,
} from '@hyperframes/engine';
// 启动启用了 WebGPU 的有头 Chrome
const { browser, page } = await launchHdrBrowser({ width: 1920, height: 1080 });
// 注入 WebGPU 回读运行时
const ok = await initHdrReadback(page, 1920, 1080);
// 对于每一帧:上传 float16 像素,回读 float16 RGBA
const { rgba16, bytesPerRow } = await uploadAndReadbackHdrFrame(page, float16Base64);
// 将线性 float16 转换为 PQ 编码的 16 位 RGB,适合通过管道传入 ffmpeg/x265
const pqRgb = float16ToPqRgb(rgba16, width, height, bytesPerRow);
⚠️ Warning
此路径需要带有
--enable-unsafe-webgpu的有头 Chrome — WebGPU 在chrome-headless-shell中不可用。默认的 HDR 感知渲染管道(通过 FFmpeg 从源中提取 HDR 像素并在 Node 中合成)不使用此路径。仅在需要 CSS 动画驱动 HDR 像素输出的高级自定义管道中使用它。
window.__hf 协议
engine 通过 window.__hf 协议与浏览器页面通信。任何实现了此协议的页面都可以被 engine 捕获 — 你不仅限于 Hyperframes 合成。
// 页面必须在 window.__hf 上暴露此接口
interface HfProtocol {
duration: number; // 总时长(秒)
seek(time: number): void; // 跳转到指定时间
media?: HfMediaElement[]; // 可选的媒体元素声明
}
interface HfMediaElement {
elementId: string; // DOM 元素 ID
src: string; // 媒体源 URL
startTime: number; // 时间轴上的开始时间
endTime: number; // 时间轴上的结束时间
mediaOffset?: number; // 源中的播放偏移
volume?: number; // 音量 (0-1)
hasAudio?: boolean; // 元素是否包含音频
}
核心概念
BeginFrame 渲染
传统屏幕录制以实际时间速度记录 — 如果你的系统负载较高,帧就会被丢弃。engine 使用 Chrome 的 HeadlessExperimental.beginFrame 来显式推进合成器,按需生成每一帧。这意味着:
- 不丢帧 — 每一帧都被捕获
- 无时间依赖 — 60 秒的视频不需要 60 秒来捕获
- 像素完美输出 — 合成器产生与其显示完全相同的像素
有关这如何实现确定性输出的更多信息,请参阅确定性渲染。
跳转约定
engine 依赖 Hyperframes 运行时的 renderSeek(time) 函数。调用时,renderSeek:
- 暂停所有 GSAP 时间轴
- 将每个时间轴跳转到精确的时间戳
- 更新所有媒体元素(视频、音频)以匹配
- 根据
data-start和data-duration挂载/卸载 clip
这个约定使得逐帧捕获成为可能 — 每一帧都是合成在该时间点的完整、独立的快照。
Chrome 要求
engine 需要 chrome-headless-shell,安装包时已包含。它使用固定的 Chrome 版本以确保跨环境一致的渲染。要获得完全确定性的输出(包括字体),请通过 producer 使用 Docker 模式。