数据库

Node + FFmpeg 实现Canvas动画导出视频

时间:2010-12-5 17:23:32  作者:域名   来源:人工智能  查看:  评论:0
内容摘要:导言Canvas为前端提供了动画展示的平台,随着现在视频娱乐的流行,你是否想过把Canvas动画导出视频?目前纯前端的视频编码转换(例如WebM Encoder Whammy)还存在许多限制,较为成熟

导言

Canvas为前端提供了动画展示的实现视频平台,随着现在视频娱乐的动画导出流行,你是实现视频否想过把Canvas动画导出视频?目前纯前端的视频编码转换(例如WebM Encoder Whammy)还存在许多限制,较为成熟的动画导出方案是将每帧图片传给后端实现,由后端调用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}); })  

效果预览 

  

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

源码库
copyright © 2025 powered by 益强资讯全景  滇ICP备2023006006号-31sitemap