By uploading pictures of appropriate sizes, users can preview the effect similar to a slideshow by selecting the effect and music of the rendering animation, and finally click to confirm to generate a video, which can be played on headlines or Douyin.
Possible solutions for generating videosPure front-end video encoding conversion (such as WebM Encoder Whammy)
Pass each frame of image to the backend for implementation, and the backend calls FFmpeg for video transcoding.
Image generation can be achieved through the canvas native interface toDataURL, and ultimately returns image data in base64 form.
function generatePng() { var canvas = document.createElement('canvas'); let icavas = '#canvas' //Canvas id for rendering animation 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;}How to take screenshots of canvas animation
Use setInterval to execute image generation method regularly. Of course, you can also use requestAnimationFrame.
setInterval(function() { imgsTemp.push(generatePng())}, 1000/60)How to get each frame of image in the backend
Solution 1: The headless browser runs the front-end canvas animation js, and then takes a screenshot of the js
Initial idea:
The screenshots are printed out using console.log. The canvas screenshots are in base64 format, a 15-second animation, and there are more than 100 screenshots, which directly caused the server to crash (rejected);
Trial run plan:
The screenshot is stored in a js variable. After the animation is played, a logo is added to the page, and then the backend gets the variable. The code is as follows:
const pages = { imageZoomOut: import ('./image_zoom_inout.js'), //Zoom imageArt: import ('./image_art.js'), //Erase imageGrid: import ('./image_grid.js'), //Grid imageRotate: import ('./image_rotate.js'), //Open and close imageFlash: import ('./image_flash.js'), //Image and text flash imageVerticalArt: import ('./image_vertical_art.js'), //Vertical erasure imageVerticalGrid: import ('./image_vertical_grid.js'), //Vertical grid imageVerticalRotate: import ('. /image_vertical_rotate.js'), //Vertical opening and closing imageVerticalFlash: import ('./image_vertical_flash.js'), //Vertical image and text flash imageVerticalZoomOut: import ('./image_vertical_zoom_inout.js'), //Vertical zoom imageVertical: import ('./image_vertical.js'), //Vertical version general};var isShow = falsevar imgsBase64 = []var imgsTemp = []var cutInter = nullvar imgsTimeLong = 0function 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. tidvar wrapWidth = getQuery.spec.templateTypelet num = 0let 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() { //After the animation is played isShow = true; imgsTemp.push(generatePng()) imgsBase64.push(imgsTemp) let now = new Date().getTime() window.imgsTimeLong = now - oldDate clearInterval(cutInter) document.getElementById('cutImg').innerHTML = ' done'//Page identification}) 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 //Screenshot storage variable creatImg()
Disadvantages of the trial operation plan:
var temp = new Image(vw, vh)temp.setAttribute('crossOrigin', 'anonymous');Final solution: run animation on NODE side
Use node-canvas to write each frame screenshot to the specified folder using fs.writeFile
const { createCanvas, loadImage} = require(canvas); const pages = { imageZoomOut: require('./image_zoom_inout.js'), //Zoom imageArt: require('./image_art.js'), //Erase imageGrid : require('./image_grid.js'), //Grid imageRotate: require('./image_rotate.js'), //Open and close imageFlash: require('./image_flash.js'), //Image and text flash imageVerticalArt: require('./image_vertical_art.js'), //Vertical erasure imageVerticalGrid: require('./image_vertical_grid .js'), //vertical grid imageVerticalRotate: require('./image_vertical_rotate.js'), //vertical grid imageVerticalFlash: require('./image_vertical_flash.js'), //vertical image and text flash imageVerticalZoomOut: require('./image_vertical_zoom_inout.js'), //vertical zoom imageVertical: require('./image_vertical.js'), //General for vertical version};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 height let vw = parse.templateType == 1 ? 1280 : 720 //canvas width let imgSrcArray = parse.images //Image array let effectTag = parse.tid //Animation effect 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) => { //Initialize animation console.log('Start animation') 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, 'Animation ends') }) 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); });
Execute the following command under 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 =../ass ets/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/'
Parameter description:
1) tid is the animation name
2) templateType is size: 1:1280*720; 2:720*1280
3) images is the image address
4) The variable './images/' is the address where the screenshot is saved,
Disadvantages of running in NODE environmentLoop the following drawing every 13 seconds:
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();
Because of the event loop model of Node.js, the use of Node.js must ensure that the cycle of Node.js can run at all times. If a very time-consuming function appears, the event loop will get stuck and cannot handle other tasks in time, so As a result, some animations are still slow
Possibility of later optimizationTry using Go language to take screenshots;
Rewrite canvas animation;
extra Video bitrateThe video bit rate is the number of data bits transmitted per unit time during data transmission. Generally, the unit we use is kbps, which is thousands of bits per second. A simple understanding is the sampling rate. The greater the sampling rate per unit time, the higher the accuracy, and the closer the processed file is to the original file. For example, for an audio, the higher the bit rate, the smaller the compression ratio, the smaller the sound quality loss, and the closer the sound quality is to the audio source.
FPS frames per second (Frames Per Second))
FPS is a definition in the field of graphics, which refers to the number of frames transmitted per second. Generally speaking, it refers to the number of frames of animation or video. FPS is a measure of the amount of information used to save and display dynamic video. The more frames per second, the smoother the action displayed. Generally, the minimum to avoid jerky movements is 30. For example, a movie is played at a speed of 24 frames per second, which means that 24 still frames are continuously projected on the screen in one second.
The above is the entire content of this article. I hope it will be helpful to everyone’s study. I also hope everyone will support VeVb Wulin Network.