@vvfx/export-media
v0.0.1-beta.20261103.9ec4c789
Published
浏览器端 Galacean Effects 场景媒体导出工具,支持 MP4、GIF、APNG、WebP、WebM、PNG 序列帧及 AlphaMaskVideo。
Keywords
Readme
@vvfx/export-media
浏览器中的 Galacean Effects 场景媒体导出工具。当前版本 0.0.1-alpha.0,输入 JSONScene,可生成 MP4、GIF、APNG、动画 WebP、WebM、PNG 序列帧 ZIP 和 AlphaMaskVideo。
更新记录见 CHANGELOG。
安装
npm install @vvfx/export-media包发布在 npm 官方源,0.0.1-alpha.0 为 alpha 预览版本,安装时可按需追加 @0.0.1-alpha.0 或 -alpha 标签。本包为浏览器端工具;运行环境要求见文末部署前提。
开发与验证
以下命令在已安装依赖的完整仓库根目录执行,开发环境要求 Node.js >= 22.12.0、pnpm >= 10。npm 安装包只包含构建产物和使用说明,不包含测试源码与配置。
# SDK 构建
pnpm --filter @vvfx/export-media build发布校验
本包与 @vvfx/artis 一样配置为发布到 npm 官方源,访问级别为 public。发布前执行上面的构建命令。
files 只包含 dist,npm 自动包含 README 与 package.json。
使用方式
发布产物的 ESM / CJS 入口首次执行时,会通过 console.info 显示包版本,例如:
[@vvfx/export-media] v0.0.1-alpha.0提示由构建配置自动读取版本信息,同一模块重复导入不会重复打印。源码开发入口、仅类型导入,以及被宿主打包器移除的导入不会触发提示;宿主若配置删除 console,也会移除提示。Artis 将 Effects 及其插件作为外部依赖,本包仍内嵌 runtime;宿主覆盖依赖版本或通过 CDN 加载 runtime 时,仍需确保版本与包声明一致。
import { Exporter } from '@vvfx/export-media';
import type { ExportItemParams, FileBuffer } from '@vvfx/export-media';
// 在浏览器中调用,传入合法的 Galacean Effects JSONScene。
export async function exportMp4 (scene: ExportItemParams['scene']): Promise<Blob> {
const exporter = new Exporter({ mediaType: 'MP4', isOutputBuffer: true });
let succeeded = false;
let outputs: FileBuffer[] | undefined;
exporter.on('progress', value => console.info('progress', value));
exporter.on('complete', (success, _infos, buffers) => {
succeeded = success;
outputs = buffers;
});
try {
await exporter.onExport([{
scene,
size: [640, 360],
name: 'animation',
folderName: 'exports',
fps: 30,
backgroundColor: '#ffffff',
}]);
if (!succeeded || !outputs?.[0]) {
throw new Error('媒体导出失败');
}
return new Blob([new Uint8Array(outputs[0])], { type: 'video/mp4' });
} finally {
exporter.dispose();
}
}onExport() 的 Promise 表示导出任务已结算;任务失败时通常仍会 resolve,必须检查 complete 的 success。它不等待自动下载 ZIP 的生成或浏览器保存完成。正在运行、已销毁等误用可能同步抛错。
isOutputBuffer: true 用于自行预览、上传或保存原始媒体字节。省略时默认自动下载 ${folderName}.zip,ZIP 内使用各任务的 ${name}.${格式后缀},即使只导出一个 MP4 也会包一层 ZIP;Images 的产物本身也是 ZIP,因此自动下载时形成两层 ZIP。complete 在自动下载前触发,两种模式都能通过该事件取得 buffers。
同一实例串行导出;不同实例可以独立工作。运行中不能 setOptions() 或再次 onExport()。取消用 onCancel(),彻底结束用 dispose()。取消和参数错误后可以重新提交任务;取消会保留此前完成的产物,包括在 done 回调中立即取消的情况。不要在 done/complete 回调中同步开始新任务;应等待上一次 onExport() 结算。
格式与限制
| mediaType | 主产物 | 编码方式 | 特性与当前限制 |
| --- | --- | --- | --- |
| Images | ZIP 内 PNG 序列帧 | Canvas + JSZip | 可保留透明度;帧名从 image-0001.png 起,目录使用合成名称 |
| MP4 | H.264 MP4 | FFmpeg WASM | yuv420p;奇数宽高向上补为偶数;支持目标时长、末帧 JPEG;音轨仅为静音 |
| GIF | GIF 动图 | FFmpeg WASM | 可调帧率、尺寸、四档质量和循环;GIF 无半透明 alpha |
| APNG | APNG 动图 | FFmpeg WASM | 可调帧率、尺寸、压缩等级和循环 |
| WebP | WebP 动图 | img2webp WASM | 相邻重复画面可能合帧;每个输入帧的时长取整到毫秒 |
| WebM | VP9 WebM | Canvas captureStream + MediaRecorder | 通过 requestFrame() 按任务调度逐帧采集;时间戳取墙上时钟,受浏览器调度影响,不能保证离线编码级别的时间精度;不捕获音轨 |
| AlphaMaskVideo | H.264 MP4 | Canvas 通道拼接 + FFmpeg | 输出宽度约为输入两倍;左侧 RGB、右侧 alpha 灰度;必须提供 extraCanvas |
透明格式必须显式设置 backgroundColor: '';省略时默认合成黑色不透明背景。AlphaMaskVideo 是通道拼接约定,不是原生带透明通道的视频,需要播放端自行还原。audioEnable 不保留场景里的原始声音。
捕获帧数为 max(1, round(场景时长 × fps)),从 t=0 开始按 1/fps 采样;末帧 JPEG 对应最后一张采样帧。WebP 使用 round(1000/fps) 毫秒作为每帧时长,例如 12 FPS 的 12 帧总计 0.996 秒;相同画面合帧不应改变总时长。GIF 的时间量化、APNG 的无损压缩和 AAC 的编码填充也应分别判断,不能只凭文件大小或严格等于十进制秒数判错。
公共 API
运行时顶层导出仅有 Exporter 和 loadFFmpegHelper。内部 ExportMedia、MEDIA_TYPE、状态常量和内部工具不是公共 API,也不能通过包 exports 深路径导入。
loadFFmpegHelper(options?)
返回 Promise<FFmpeg>,其中 FFmpeg 来自 @ffmpeg/ffmpeg,成功返回时已加载编码内核。唯一选项是 multiThreading?: boolean,默认 false;每次调用都会创建新的 FFmpeg 实例。需要自行执行 FFmpeg 命令时可使用它,结束后由调用方调用 terminate() 释放 Worker 和加载时创建的 Blob URL。多线程资源 URL 会保留到终止时,供后续编码创建线程使用。
Exporter 会按导出格式自行加载内核,使用它不需要提前调用此函数。helper 当前没有加载超时、重试或自定义资源 URL 参数。初始化中取消/销毁 Exporter 不会中断已经开始的资源下载;迟到的内核会在初始化返回后释放,不会重新激活实例。
初始化选项 ExportMediaInitOptions
| 字段 | 默认 | 说明 |
| --- | --- | --- |
| mediaType | 必填 | 上表七个区分大小写的值 |
| isOutputBuffer | false | true 时不自动下载;完成事件仍提供 buffers |
| imageQuality | 捕获时回落为 1 | Canvas 图片编码质量,0–1;主要影响 MP4 中间 JPEG、WebP 输入帧 |
| extraCanvas | 无 | AlphaMaskVideo 使用的辅助 HTMLCanvasElement |
| loggerInTranscoding | false | 打印捕获及 FFmpeg 日志;不是结构化业务事件 |
| multiThreading | false | 请求 FFmpeg MT;要求跨源隔离和 SharedArrayBuffer,否则明确拒绝加载 |
任务 ExportItemParams
| 字段 | 默认 | 说明 |
| --- | --- | --- |
| scene | 必填 | 合法 JSONScene,加载场景时会深复制 |
| size | 必填 | 完整场景的渲染尺寸 [width, height];调用方应保证为正整数且满足内存预算 |
| crop | 无 | { x, y, width, height };以 size 的左上角为原点逐帧裁剪,范围必须在完整画面内;输出为裁剪尺寸(向上取整,MP4 再补齐偶数),不改变相机与布局 |
| name / folderName | 必填 | 文件名 / 自动下载 ZIP 名;批量 ZIP 使用第一项 folderName |
| fps | 30 | 正有限数;决定场景捕获帧数 |
| time | 无 | 正有限的目标时长,单位秒;MP4/AlphaMaskVideo 保留全部采样帧,按“帧数 / 目标时长”设置编码帧率;不是裁剪区间,其他格式不使用此选项 |
| backgroundColor | '#000000' | 空字符串用于保留透明背景 |
| loop | true | GIF/APNG/WebP 的循环播放设置 |
| audioEnable | false | 仅 MP4 为当前任务生成匹配时长的静音 WAV,再封装 AAC;AAC 解码可能带少量编码填充 |
| mp4Config.isExportLastFrameJPEG | false | 末帧 JPEG 放在 extras 对应位置 |
| gifConfig / apngConfig | 可省略 | { fps?, scale?, quality? };输出 fps 默认跟随任务 FPS,scale 默认 '-1:-1',quality 默认 'highest';质量可选 highest/high/medium/low |
任务 fps 决定捕获帧数,gifConfig.fps / apngConfig.fps 只在编码时调整输出帧率。scale 使用 FFmpeg 的尺寸表达式,例如 '320:-1';四档 quality 与初始化选项 imageQuality 是不同参数。
time、scale、尺寸、质量与资源 URL 的防御性校验尚不完整。不要把用户输入未经校验直接作为这些配置。
方法与事件
| API | 含义 |
| --- | --- |
| status | default / exporting / success / error |
| setOptions(partial) | 空闲时更新选项;改变 mediaType 或 multiThreading 时重新准备相应 helper |
| onExport(params): Promise<void> | 串行执行一批任务;结果由事件提供 |
| onCancel() | 取消活动任务;没有活动任务时直接返回 |
| dispose() | 幂等释放资源;实例随后不再可用 |
| downloadByMediaBuffers(params, buffers, extras) | 将主文件与额外 JPEG 打包成 ZIP 并下载;无额外文件时传 null;返回 void,不能用 await 等待下载完成 |
| on/off/getListeners | 注册、移除、查询事件监听器 |
| progress(value) | 数值通常为 0–1;取消/失败也可能发出 1,不能将 1 等同成功 |
| done(item, success, buffer?, extra?) | 单项结果;部分异常路径没有 done |
| complete(success, taskInfos, buffers, extras) | 整批结果;失败的批量结果可能含空缺,取消保留已完成产物 |
Exporter 没有公开 error/ready 事件。可直接导入的类型有 ExporterEvent、ExportItemParams、ExportMediaCrop、ExportParams、ExportMediaInitOptions、ExportMediaItemDownloadInfo、FileBuffer、Buffers、Extras、LoadFFmpegHelperOptions。配置子类型尚未从顶层导出,可通过 ExportItemParams['gifConfig'] 等索引获取。
部署前提
- 浏览器具备 DOM、Canvas、WebGL、WebAssembly、Worker;此包不是 Node.js 服务端转码器。
- WebM 还需要
canvas.captureStream()、CanvasCaptureMediaStreamTrack.requestFrame()和MediaRecorder的 VP9 支持,当前没有能力降级。 - 单线程 FFmpeg 不要求跨源隔离。多线程要求安全上下文、
SharedArrayBuffer和正确的 COOP/COEP。 - 当前 helper 地址集中在
src/media-export/config.ts,来自gw.alipayobjects.com、mdn.alipayobjects.com;没有公共自托管 URL 配置、下载进度、超时和重试参数。 - CSP 需要允许相关网络请求、Blob Worker,以及 WebP 脚本;根据实际部署策略配置,不建议笼统放开全部源。
- 跨域图片、视频需正确设置 CORS,否则画布编码可能失败;大型项目还需要评估帧缓冲、WASM 和 ZIP 的峰值内存。
- 包声明了多种 GE 插件依赖,但没有主动注册这些插件,dist 还内联了 GE runtime。不能假定“依赖已安装”或“宿主已导入插件”就能为内嵌 runtime 完成注册;应验证真实插件素材。此次只验证了基础 Sprite 场景。
仓库当前有两处直接使用本包:Agent Web 在 apps/web/src/features/canvas/canvas-export-service.ts 中完成画布截图与视频导出;Artis 演示(web-packages/artis)在 artis-page-shell 的 media-export.ts / export.ts 中提供场景与特效元素的视频导出入口,分别通过 createSceneVideoExportParams / createEffectsVideoExportParams 构造任务参数,安全区导出使用 crop 裁剪实现。Artis 包内的旧导出门面(src/features/export-media.ts 及 export-operations.ts 中的相关逻辑)仍处于注释状态。
