用戶透過上傳合適尺寸的圖片,選著渲染動畫的效果和音樂,可以預覽類似幻燈片的效果,最後點擊確認生成視頻,可以放到頭條或者抖音播放。
產生視訊可能的方案純前端的視訊編碼轉換(例如WebM Encoder Whammy)
將每幀圖片傳給後端實現,由後端調用FFmpeg進行視訊轉碼
圖片產生可以透過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 = 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得到== 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) } mapelse {ima. (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 = '#verticalvas' } letCanvas' } 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 = '#verticalvas' } 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/Nodep. ; return imgData;}window.imgsBase64 = imgsBase64 //截圖儲存變數creatImg()
試運行方案的弊端:
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 loadArrray = []imgrcArray .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 =../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/'
參數說明:
1)tid 是動畫名稱
2)templateType是尺寸:1:1280*720;2:720*1280
3) images是圖片地址
4)變數'./images/'是截圖保存的位址,
NODE環境下運作的弊端每隔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張靜止畫面。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。