将HTML Canvas/WebGL动画服务器端记录为视频的最佳方式



我有一组动画,我可以在Canvas (fabric.js)或WebGL (three.js)。我需要在服务器端通过脚本自动记录它们并输出视频文件。


  1. 图片
  2. 视频(含音频)
  3. <
  4. 其他动画/影响/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使这非常容易。

最新更新