java mp4视频转换成h5_前端canvas动画如何转成mp4视频的方法

本文介绍了一种将canvas动画转换为视频的方法,通过在服务器端运行canvas动画并截图,再利用FFmpeg合并图片生成视频。前端通过上传图片和选择效果,后端利用无头浏览器和node-canvas进行处理,最后返回视频下载URL。
摘要由CSDN通过智能技术生成

用户通过上传合适尺寸的图片,选着渲染动画的效果和音乐,可以预览类似幻灯片的效果,最后点击确认生成视频,可以放到头条或者抖音播放。

b4f3469791eb73b635712dba8bef2c6f.png

生成视频可能的方案

纯前端的视频编码转换(例如WebM Encoder Whammy)

图片地址只能是相对地址

音乐不能收录

生成的视频需要下载再上传

将每帧图片传给后端实现,由后端调用FFmpeg进行视频转码

截图多的时候,base64字符串形式的图片太大,在前端不好传给后端

在前端截图还依赖用户电脑性能;

最后定的方案流程

canvas动画和截图在服务器端运行,后端根据标识获取截图

利用FFmpeg将图片合并成视频,并将视频存储在server端,并返回相应下载url

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

前端canvas如何截图

每帧图片生成

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

function generatePng() {

var canvas = document.createElement('canvas');

let icavas = '#canvas' //渲染动画的canvas id

if (wrapWidth == 2) {

icavas = '#verticalCanvas'

}

var canvasNode = document.querySelector(icavas)

canvas.width = canvasNode.width;

canvas.height = canvasNode.height;

var ctx = canvas.getContext('2d');

ctx.drawImage(canvasNode, 0, 0);

var imgData = canvas.toDataURL("image/png");

return imgData;

}

canvas动画截图的方法

用setInterval定时执行图片生成的方法,当然也可以用requestAnimationFrame

setInterval(function() {

imgsTemp.push(generatePng())

}, 1000/60)

后端如何获取每帧图片

方案一:无头浏览器运行前端canvas动画js,然后js截图

最初设想:

截图用console.log打印出来,canvas截图是base64格式的,一个15秒的动画,截图有100多张,直接导致服务器运行崩溃(被否了);

试运行方案:

截图存储在js变量中,动画播放完成,在页面中加一个标识,然后后端去取这个变量,代码如下:

const pages = {

imageZoomOut: import ('./image_zoom_inout.js'), //缩放

imageArt: import ('./image_art.js'), //擦除

imageGrid: import ('./image_grid.js'), //网格

imageRotate: import ('./image_rotate.js'), //开合

imageFlash: import ('./image_flash.js'), //图文快闪

imageVerticalArt: import ('./image_vertical_art.js'), //竖版擦除

imageVerticalGrid: import ('./image_vertical_grid.js'), //竖版网格

imageVerticalRotate: import ('./image_vertical_rotate.js'), //竖版开合

imageVerticalFlash: import ('./image_vertical_flash.js'), //竖版图文快闪

imageVerticalZoomOut: import ('./image_vertical_zoom_inout.js'), //竖版缩放

imageVertical: import ('./image_vertical.js'), //竖版通用

};

var isShow = false

var imgsBase64 = []

var imgsTemp = []

var cutInter = null

var imgsTimeLong = 0

function getQuerys(tag) {

let queryStr = window.location.search.slice(1);

let queryArr = queryStr.split('&');

let query = [];

let spec = {}

for (let i = 0, len = queryArr.length; i < len; i++) {

let queryItem = queryArr[i].split('=');

let qitem = decodeURIComponent(queryItem[1])

if (queryItem[0] == tag) {

query.push(qitem);

} else {

spec[queryItem[0]] = qitem

}

}

return { list: query, spec: spec };

}

var getQuery = getQuerys('images')

var effectTag = getQuery.spec.tid

var wrapWidth = getQuery.spec.templateType

let num = 0

let imgArr = []

function creatImg() {

var images = getQuery.list

let newImg = []

let vh = wrapWidth == 1 ? 360 : 640

let vw = wrapWidth == 1 ? 640 : 360

if (effectTag.indexOf('Flash') > -1) {

images.map(function(item, index) {

if (11 === index || 13 === index || 16 === index) {

var temp = new Image(vw, vh)

temp.setAttribute('crossOrigin', 'anonymous');

temp.src = item;

newImg.push(temp)

} else {

newImg.push(item)

}

})

imgArr = newImg

renderAnimate(effectTag)

} else {

images.map(function(item) {

var temp = new Image(vw, vh)

temp.setAttribute('crossOrigin', 'anonymous');

temp.src = item;

temp.onload = function() {

num++

if (num == images.length) {

renderAnimate(effectTag)

}

}

newImg.push(temp)

})

imgArr = newImg

}

}

async function renderAnimate(page) {

//await creatImg()

let me = this

const pageA = await pages[page];

let oldDate = new Date().getTime()

let icavas = '#canvas'

if (wrapWidth == 2) {

icavas = '#verticalCanvas'

}

let innerCanvas = document.querySelector(icavas)

isShow = false

pageA[page].render(null, {

canvas: innerCanvas,

images: imgArr

}, function() {

//动画播完

isShow = true;

imgsTemp.push(generatePng())

imgsBase64.push(imgsTemp)

let now = new Date().getTime()

window.imgsTimeLong = now - oldDate

clearInterval(cutInter)

document.getElementById('cutImg').innerHTML = 'done'//页面标识

})

cutInter = setInterval(function() {

imgsTemp.push(generatePng())

if (imgsTemp.length >= 50) {

imgsBase64.push(imgsTemp)

imgsTemp = []

}

}, 130)

}

function getImgs() {

return imgsBase64

}

function generatePng() {

var canvas = document.createElement('canvas');

let icavas = '#canvas'

if (wrapWidth == 2) {

icavas = '#verticalCanvas'

}

var canvasNode = document.querySelector(icavas)

canvas.width = canvasNode.width;

canvas.height = canvasNode.height;

var ctx = canvas.getContext('2d');

ctx.drawImage(canvasNode, 0, 0);

var imgData = canvas.toDataURL("image/png");

return imgData;

}

window.imgsBase64 = imgsBase64 //截图存储变量

creatImg()

试运行方案的弊端:

截图间隔130ms截一张图片,截图数量太少,导致生成的动画不流畅;

截图间隔调成1秒60帧的话,动画播放缓慢,导致生成视频时间变长;(settimeout和setinterval的机制)

图片尺寸在640x360或者360x640,生成的动画在手机端预览不清晰;

需求换成图片尺寸为1280x720或者720x1280之后,原本15秒的动画在服务器端执行变成了70多秒

canvas截图存在跨域问题,可以如下设置

var temp = new Image(vw, vh)

temp.setAttribute('crossOrigin', 'anonymous');

最终方案:在NODE端运行动画

用node-canvas,把每帧截图用 fs.writeFile 写到指定的文件夹里

const {

createCanvas,

loadImage

} = require("canvas");

const pages = {

imageZoomOut: require('./image_zoom_inout.js'), //缩放

imageArt: require('./image_art.js'), //擦除

imageGrid: require('./image_grid.js'), //网格

imageRotate: require('./image_rotate.js'), //开合

imageFlash: require('./image_flash.js'), //图文快闪

imageVerticalArt: require('./image_vertical_art.js'), //竖版擦除

imageVerticalGrid: require('./image_vertical_grid.js'), //竖版网格

imageVerticalRotate: require('./image_vertical_rotate.js'), //竖版开合

imageVerticalFlash: require('./image_vertical_flash.js'), //竖版图文快闪

imageVerticalZoomOut: require('./image_vertical_zoom_inout.js'), //竖版缩放

imageVertical: require('./image_vertical.js'), //竖版通用

};

const fs = require("fs");

const querystring = require('querystring');

let args = process.argv && process.argv[2]

let parse = querystring.parse(args)

let vh = parse.templateType == 1 ? 720 : 1280 //canvas 高

let vw = parse.templateType == 1 ? 1280 : 720 //canvas 宽

let imgSrcArray = parse.images //图片数组

let effectTag = parse.tid //动画效果

let saveImgPath = process.argv && process.argv[3]

let loadArr = []

imgSrcArray.forEach(element => {

if (/\.(jpg|jpeg|png|JPG|PNG)$/.test(element)) {

loadArr.push(loadImage(element))

} else {

loadArr.push(element)

}

});

const canvas = createCanvas(vw, vh);

const ctx = canvas.getContext("2d");

Promise.all(loadArr)

.then((images) => {

//初始化动画

console.log('开始动画')

let oldDate = new Date().getTime()

pages[effectTag].render(null, {

canvas: canvas,

images: images

}, function() {

clearInterval(interval)

let now = new Date().getTime()

console.log(now - oldDate, '动画结束')

})

const interval = setInterval(

(function() {

let x = 0;

return () => {

x += 1;

ctx.canvas.toDataURL('image/jpeg', function(err, png) {

if (err) {

console.log(err);

return;

}

let data = png.replace(/^data:image\/\w+;base64,/, '');

let buf = new Buffer(data, 'base64');

fs.writeFile(`${saveImgPath}${x}.jpg`, buf, {}, (err) => {

console.log(x, err);

return;

});

});

};

})(),

1000 / 60

);

})

.catch(e => {

console.log(e);

});

在iterm下执行下面命令

node testCanvas.js 'tid=imageArt&templateType=1&images=../assets/imgs/8.png&images=../assets/imgs/6.png&images=../assets/imgs/7.png&images=../assets/imgs/6.png&images=../assets/imgs/8.png&images=../assets/imgs/7.png&images=../assets/imgs/4.png&images=../assets/imgs/6.png&images=../assets/imgs/8.png&images=../assets/imgs/7.png' './images/'

参数说明:

1)tid 是动画名称

2)templateType是尺寸:"1":1280*720;"2":720*1280

3) images是图片地址

4)变量'./images/'是截图保存的地址,

NODE环境下运行的弊端

参数图片地址只能是相对地址

动画过于复杂时,运行时间长,如下:当页面的图形数量达到一定时,动画每一帧就要大量调用canvas的API,要进行大量的计算,再加上图片体积很大,就会慢

每隔13秒循环一次下面的画图:

for (var A = 0; 50 > A; A++)

p.beginPath(),

p.globalAlpha = 1 - A / 49,

p.save(),

p.arc(180,320,P + 2 * A, 0, 2 * Math.PI),

p.clip(),

p.drawImage(x[c], 0, 0, y.width, y.height),

p.restore(),

p.closePath();

for (var S = 0; 50 > S; S++)

p.beginPath(),

p.globalAlpha = 1 - S / 49,

p.save(),

p.rect(0, 0, d + P + 2 * S, g + b + 2 * S),

p.clip(),

p.drawImage(x[c], 0, 0, y.width, y.height),

p.restore(),

p.closePath();

因为Node.js 的事件循环模型,要求 Node.js 的使用必须时刻保证 Node.js 的循环能够运转,如果出现非常耗时的函数,那么事件循环就会陷入进去,无法及时处理其他的任务,所以导致有些动画还是慢

后期优化的可能

尝试用go语言,来截图;

重写canvas动画;

番外

视频码率

视频码率就是数据传输时单位时间传送的数据位数,一般我们用的单位是kbps即千位每秒。通俗一点的理解就是取样率,单位时间内取样率越大,精度就越高,处理出来的文件就越接近原始文件。举例来看,对于一个音频,其码率越高,被压缩的比例越小,音质损失越小,与音源的音质越接近。

FPS 每秒传输帧数(Frames Per Second))

FPS是图像领域中的定义,是指画面每秒传输帧数,通俗来讲就是指动画或视频的画面数。FPS是测量用于保存、显示动态视频的信息数量。每秒钟帧数愈多,所显示的动作就会愈流畅。通常,要避免动作不流畅的最低是30。例如电影以每秒24张画面的速度播放,也就是一秒钟内在屏幕上连续投射出24张静止画面。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

其实前端动画Canvas 是可以结合起来使用的,而且 Canvas 绘制透明的图片也很简单。以下是示例代码: ```html <!DOCTYPE html> <html> <head> <title>Canvas 绘制透明的图片</title> </head> <body> <canvas id="myCanvas"></canvas> <script> const canvas = document.getElementById('myCanvas'); const context = canvas.getContext('2d'); // 创建一个 Image 对象 const img = new Image(); // 设置图片源 img.src = 'https://example.com/transparent-image.png'; // 在图片加载完成后绘制图片 img.onload = function() { // 将画布设置为图片大小 canvas.width = img.width; canvas.height = img.height; // 绘制图片 context.drawImage(img, 0, 0); }; </script> </body> </html> ``` 以上代码中,我们先创建了一个 Canvas 元素,并获取了它的上下文对象。接着创建了一个 Image 对象并设置了图片源。在图片加载完成后,我们将画布的尺寸设置为图片的尺寸,并使用 `drawImage` 方法将图片绘制到画布上。 如果需要实现动画效果,可以使用 `requestAnimationFrame` 方法来更新 Canvas 上的图像。例如: ```html <!DOCTYPE html> <html> <head> <title>Canvas 绘制透明的图片</title> </head> <body> <canvas id="myCanvas"></canvas> <script> const canvas = document.getElementById('myCanvas'); const context = canvas.getContext('2d'); // 创建一个 Image 对象 const img = new Image(); img.src = 'https://example.com/transparent-image.png'; // 定义图片的位置和速度 let x = 0; let y = 0; let vx = 5; let vy = 5; // 定义更新画面的函数 function update() { // 擦除画布 context.clearRect(0, 0, canvas.width, canvas.height); // 绘制图片 context.drawImage(img, x, y); // 更新图片位置 x += vx; y += vy; // 检查是否撞到画布边缘 if (x < 0 || x + img.width > canvas.width) { vx = -vx; } if (y < 0 || y + img.height > canvas.height) { vy = -vy; } // 在下一帧更新画面 requestAnimationFrame(update); } // 在图片加载完成后开始动画 img.onload = function() { canvas.width = img.width; canvas.height = img.height; update(); }; </script> </body> </html> ``` 以上代码中,我们定义了 `update` 函数来更新 Canvas 上的图像。在每一帧更新时,我们先擦除画布,然后绘制图片,并根据速度更新图片的位置。最后检查图片是否撞到画布边缘,如果是则反速度。在下一帧更新时再次调用 `update` 函数。在图片加载完成后,我们将画布的尺寸设置为图片的尺寸,并开始动画。 希望以上代码可以帮助你理解如何在 Canvas 上绘制透明的图片。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值