我有一组动画,我可以在Canvas (fabric.js)或WebGL (three.js)。我需要在服务器端通过脚本自动记录它们并输出视频文件。
- 图片
- 视频(含音频)
- 其他动画/影响/gh>
在过去的几个月里,我对此做了很多研究。
结果
1. 使用PhantomJS + FFMPEG
在无头浏览器(PhantomJS)上运行HTML Canvas动画,并使用FFMPEG记录。这里的问题是PhantomJS既不支持WebGL也不支持Video元素。 http://phantomjs.org/supported-web-standards.html
2. 使用Websockets使用DataURL将数据发送回服务器
在这里,我们需要在浏览器上运行动画(,我们不能,因为我们必须做的一切都在服务器上)。3.使用node-canvas
这是TJ Holowaychuk的一个库,它允许在Node.js上渲染HTML画布。但它有自己的局限性,而且我对这个领域的研究还不多。(如果有人能透露更多关于这个图书馆的信息)
如果有人以前做过或者可以指导我有用的地方。
我们所需要做的就是使用一些数据来创建动画并将其录制成视频,一切都在服务器端。
您可以使用electron来呈现WebGL页面,并将BrowserWindow选项"show"设置为false和/或使用xvfb-run来无头运行。
我不认为node-canvas支持webgl上下文,所以你会必须使用围绕2d绘图构建的库,当然不支持任何视频编解码器。
如果你可以让你的动画工作使用节点画布,你可以以适合您内容的速率抓取动画帧,像这样:
披露:我已经成功地使用FFmpeg编码序列外部生成的图像,但还没有尝试setInterval()下面的方法。除了动画本身的开销,我没有
// assuming "canvas" is asynchronously drawn on some interval
function saveCanvas(canvas, destFile) {
return new Promise((resolve, reject) => {
const ext = path.extname(destFile),
encoder = '.png' === ext ? 'pngStream'
: 'jpegStream';
let writable = fs.createWriteStream(destFile),
readable = canvas[encoder]();
writable
.on('finish', resolve)
.on('error', err => {
let msg = `cannot write "${destFile}": ${err.message}`;
reject(new Error(msg));
});
readable
.on('end', () => writable.end())
.on('error', err => {
let msg = `cannot encode "${destFile}": ${err.message}`;
reject(new Error(msg));
});
readable.pipe(writable);
});
}
const FPS = 30;
let frame = 0,
tasks = [],
interval = setInterval(() => tasks.push(
saveCanvas(canvas, `frame_${frame++}.png`)), 1000 / FPS);
// when animation is done, stop timer
// and wait for images to be written
clearInterval(interval);
Promise.all(tasks).then(encodeVideo);
function encodeVideo() {
// too much code to show here, but basically run FFmpeg
// externally with "-i" option containing "frame_%d.png"
// and "-r" = FPS. If you want to encode to VP9 + WEBM,
// definitely see: http://wiki.webmproject.org/ffmpeg/vp9-encoding-guide
}
然后使用FFmpeg将一系列图像编码为视频。
对于encodeVideo()
背后的代码,您可以查看这个示例。
Edit: canvas.pngStream()
写入可能有问题动画循环持续绘制时不正确的帧那一个画布,也许需要创建一个画布的副本每帧?这肯定会造成很大的内存压力。
我认为chromium headless模式可能已经支持WebGL了,这是另一种可能性。视频渲染部分还没有出来:https://bugs.chromium.org/p/chromium/issues/detail?id=781117
CCapture.js使这非常容易。