博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Node + FFmpeg 实现Canvas动画导出视频
阅读量:6510 次
发布时间:2019-06-24

本文共 3901 字,大约阅读时间需要 13 分钟。

导言

Canvas为前端提供了动画展示的平台,随着现在视频娱乐的流行,你是否想过把Canvas动画导出视频?目前纯前端的视频编码转换(例如WebM Encoder )还存在许多限制,较为成熟的方案是将每帧图片传给后端实现,由后端调用FFmpeg进行视频转码。整体流程并不复杂,这篇文章将带大家实现这个过程。

整体方案

  • 由前端记录Canvas动画的每帧图像,以base64字符串形式传给后端

  • 利用node fluent-ffmpeg模块,调用FFmpeg将图片合并成视频,并将视频存储在server端,并返回相应下载url

  • 前端通过请求得到视频文件

前端部分

每帧图片生成

图片生成可以通过canvas原生接口toDataURL实现,最终返回base64形式的图像数据。

generatePng () {  ...  var imgData = canvas.toDataURL("image/png");  return imgData;}

动画录制与图片流传输

动画的记录与传送是个异步过程,这里返回一个Promise,等待后端处理完毕,收到回应后,即完成此异步过程。

以下代码将canvas每帧动画信息存入一个图片数组imgs中,将数组转成字符串的形式传给后端。注意这里contentType设置为“text/plain”。

generateVideo () {  var that = this;  return new Promise (    function (resolve, reject) {      var imgs = [];      ...      window.requestAnimationFrame(that.recordTick.bind(that, imgs, resolve, reject));    }  )}
recordTick (imgs, resolve, reject) {  ...//每帧动画的记录信息,如时间戳等  if (...) {//动画终止条件    this.stopPlay();    imgs.push(this.generatePng());    $.ajax({      url: '/video/record',      data: imgs.join(' '),      method: 'POST',      contentType: 'text/plain',      success: function (data, textStatus, jqXHR) {        resolve(data);      },      error: function (jqXHR, textStatus, errorThrown) {        reject(errorThrown);      }    });  } else {    ...//每帧动画展示的代码    imgs.push(this.generatePng());    window.requestAnimationFrame(this.recordTick.bind(this, imgs, resolve, reject));  }}

视频下载

上一节代码中,动画停止时,会通过post请求给后端传送所有图片数据,后端处理完毕后,返回数据中包含一个url,此url即为视频文件的下载地址。

为了支持浏览器端用户点击下载,我们需要用到a标签的download属性,此属性可以支持点击a标签后下载指定文件。

editor.generateVideo().then(function (data) {  videoRecordingModal.setDownloadLink(data.url, data.filename);  videoRecordingModal.changeStatus('recorded');});
setDownloadLink: function (url, filename) {  this.config.$dom.find('.video-download').attr('href', url);  this.config.$dom.find('.video-download').attr('download', filename);}

后端部分

图片序列生成

接收到前端传送的图片数据后,我们首先需要将图片解析、存储在服务器中,我们建立以当前时间戳命名的文件夹,将图片序列以一定格式存储于其中。由于每张图片写入都是异步过程,为确保所有图片都已处理完毕后,才执行视频转码过程,我们需要用到Promise.all。

Promise.all(imgs.map(function (value, index) {  var img = decodeBase64Image(value)  var data = img.data  var type = img.type  return new Promise(function (resolve, reject) {    fs.writeFile(path.resolve(__dirname, (folder + '/img' + index + '.' + type)), data, 'base64', function(err) {      if (err) {        reject(err)      } else {        resolve()      }    })  })})).then(function () {  …//视频转码})

其中decodeBase64Image函数参考。

视频生成

视频生成利用FFmpeg转码工具。

首先确保server端安装了FFmpeg

brew install ffmpeg

在项目中安装fluent-ffmpeg,这是node调用ffmpeg的接口模块

npm install fluent-ffmpeg --save

结合上一节图片序列存储的代码,整个接口代码如下:

app.post('/video/record', function(req, res) {  var imgs = req.text.split(' ')  var timeStamp = Date.now()  var folder = 'images/' + timeStamp  if (!fs.existsSync(resolve(folder))){    fs.mkdirSync(resolve(folder));  }  Promise.all(imgs.map(function (value, index) {    var img = decodeBase64Image(value)    var data = img.data    var type = img.type    return new Promise(function (resolve, reject) {      fs.writeFile(path.resolve(__dirname, (folder + '/img' + index + '.' + type)), data, 'base64', function(err) {        if (err) {          reject(err)        } else {          resolve()        }      })    })  })).then(function () {    var proc = new ffmpeg({ source: resolve(folder + '/img%d.png'), nolog: true })      .withFps(25)      .on('end', function() {        res.status(200)        res.send({          url: '/video/mpeg/' + timeStamp,          filename: 'jianshi' + timeStamp + '.mpeg'        })      })      .on('error', function(err) {        console.log('ERR: ' + err.message)      })      .saveToFile(resolve('video/jianshi' + timeStamp + '.mpeg'))  })})

视频下载

最终将视频文件传输给前端的接口代码如下:

app.get('/video/mpeg/:timeStamp', function(req, res) {  res.contentType('mpeg');  var rstream = fs.createReadStream(resolve('video/jianshi' + req.params.timeStamp + '.mpeg'));  rstream.pipe(res, {end: true});})

效果预览

图片描述

注:此功能是个人项目”简诗”的一部分,完整代码可以查看

转载地址:http://okdfo.baihongyu.com/

你可能感兴趣的文章
win7 下硬盘安装Redhat7
查看>>
js图表控件:highcharts的应用
查看>>
Redis 分布式锁的正确实现方式
查看>>
mysqldump 备份命令使用中的一些经验总结
查看>>
Linux下MySql安装配置方法总结
查看>>
本IT博客用于域名投资、互联网、资源下载等相关干货收藏和学习
查看>>
ArrayList底层实现
查看>>
【转载】Java程序设计入门 (二)
查看>>
which、whereis、location和fand的区别
查看>>
IP地址和子网划分学习笔记之《子网掩码详解》
查看>>
单词最近距离
查看>>
高性能网站性能优化与系统架构(ZT)
查看>>
程序猿知道英语词汇
查看>>
数据存储(两)--SAX发动机XML记忆(附Demo)
查看>>
ECSHOP添加购物车加图片飞入效果
查看>>
谈谈SQL 语句的优化技术
查看>>
数据结构Java实现04----循环链表、仿真链表
查看>>
ecshop如何判断缓存文件是否能更新
查看>>
javascript于boolean类型转换,运营商&&和|| 返回值
查看>>
Socket tips: UDP Echo service - Client code
查看>>