亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > HTML > 正文

前端canvas動畫如何轉成mp4視頻的方法

2024-08-26 00:21:31
字體:
來源:轉載
供稿:網友

用戶通過上傳合適尺寸的圖片,選著渲染動畫的效果和音樂,可以預覽類似幻燈片的效果,最后點擊確認生成視頻,可以放到頭條或者抖音播放。

canvas,動畫,mp4,視頻

生成視頻可能的方案

純前端的視頻編碼轉換(例如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 = 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() {        //動畫播完        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張靜止畫面。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到HTML教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日本久久久久久久久久久| 欧美日韩亚洲91| 亚洲精品国产精品国自产观看浪潮| 91av视频在线免费观看| 91久久久久久久久久久久久| 亚洲欧美日韩在线一区| 精品久久久久久中文字幕| 亚洲午夜未满十八勿入免费观看全集| 国产精品视频公开费视频| 久久亚洲国产成人| 国产成人综合精品在线| 97在线观看视频国产| 国产精品高精视频免费| 国产精品wwww| 欧美视频一区二区三区…| 日韩高清免费在线| 色777狠狠综合秋免鲁丝| 91tv亚洲精品香蕉国产一区7ujn| 欧美在线精品免播放器视频| 午夜精品福利在线观看| 中文字幕亚洲在线| 亚洲图片欧洲图片av| 欧洲一区二区视频| 97视频在线看| 成人激情视频在线播放| 久久久中精品2020中文| 日本精品视频在线| 91手机视频在线观看| 亚洲直播在线一区| 668精品在线视频| 日韩在线中文字| www.国产一区| 97国产一区二区精品久久呦| 海角国产乱辈乱精品视频| 国产一区二区三区中文| 亚洲一区二区自拍| 日韩欧美国产一区二区| 红桃视频成人在线观看| 欧美剧在线观看| 国产精品电影久久久久电影网| 欧美激情视频免费观看| 国产欧美一区二区三区久久人妖| 视频一区视频二区国产精品| 亚洲成人网在线观看| 国产精品国产三级国产aⅴ浪潮| 日韩在线视频免费观看| 精品国产一区二区三区久久狼黑人| 欧美国产精品日韩| 欧美在线视频播放| 日韩一区二区精品视频| 国产成人一区二区三区电影| 国产精品女主播| 国产精品影院在线观看| 久久久久久久爱| 国产精品久久久久国产a级| 7777免费精品视频| 91精品久久久久| 中文字幕亚洲第一| 日本亚洲欧美成人| 欧美成在线视频| 欧美亚洲国产日韩2020| 国产成人极品视频| 啪一啪鲁一鲁2019在线视频| 亚洲福利在线视频| 国产精品福利久久久| 中文字幕精品在线| 日韩精品在线观| 精品视频在线播放免| 一区二区三区四区在线观看视频| 青草青草久热精品视频在线观看| 亚洲成av人乱码色午夜| 亚洲国产精品久久| 亚洲精品videossex少妇| 亚洲的天堂在线中文字幕| 欧美日韩中文字幕在线| 国产在线拍偷自揄拍精品| 欧美午夜女人视频在线| 伊是香蕉大人久久| 国产精品久久久久久久美男| 亚洲精品永久免费| 亚洲欧美一区二区三区四区| 久久理论片午夜琪琪电影网| 国产精品久久久久久亚洲调教| 国产乱肥老妇国产一区二| 亚洲精品日产aⅴ| 亚洲国产精品专区久久| 2024亚洲男人天堂| 欧美性在线视频| 欧美高清视频在线播放| 亚洲丁香婷深爱综合| 日韩视频第一页| 亚洲欧美日韩视频一区| 91情侣偷在线精品国产| 国产亚洲精品美女久久久| 欧美激情a∨在线视频播放| 5278欧美一区二区三区| 色妞在线综合亚洲欧美| 91最新国产视频| 国产噜噜噜噜噜久久久久久久久| 欧美大奶子在线| 亚洲图片在线综合| 久久成人这里只有精品| 国产成人精品久久二区二区91| 欧美精品久久久久久久久| 欧美激情性做爰免费视频| 中文字幕视频在线免费欧美日韩综合在线看| 欧美高清视频在线| 欧美裸体视频网站| 日韩亚洲第一页| 国产精品高潮呻吟久久av黑人| 91在线免费网站| 久久这里只有精品视频首页| 亚洲一区制服诱惑| 国产亚洲日本欧美韩国| 亚洲精品免费一区二区三区| 色中色综合影院手机版在线观看| 自拍偷拍亚洲一区| 97婷婷大伊香蕉精品视频| 国产精品一区二区女厕厕| 美女久久久久久久| 中文字幕亚洲二区| 亚洲国产精品字幕| 国产97在线播放| 欧美亚洲视频在线看网址| 91啪国产在线| 欧美劲爆第一页| 中文字幕在线看视频国产欧美在线看完整| 日韩av大片在线| 亚洲色图35p| 色综合久久88色综合天天看泰| 97成人精品区在线播放| 26uuu日韩精品一区二区| 欧美xxxx做受欧美.88| 日韩国产一区三区| 久久精品视频在线| 亚洲第一视频网| 欧美日韩成人在线视频| 亚洲一区国产精品| 亚洲成人av中文字幕| 日韩在线资源网| 91夜夜揉人人捏人人添红杏| 日日狠狠久久偷偷四色综合免费| 精品久久久久久亚洲国产300| xvideos亚洲人网站| 国语自产偷拍精品视频偷| 日韩一区二区精品视频| 欧美黑人性猛交| 国产成人一区二区三区电影| 亚洲人成网站色ww在线| 91精品在线观看视频| 一区二区三区四区精品| 亚洲成人黄色在线观看| 亚洲第一网站免费视频| 九九精品在线观看| 国产精品igao视频| 亚洲直播在线一区| 久久国产精品久久久久| 黄色一区二区三区| 久久精品电影网站| 欧美亚洲视频一区二区| 精品国产鲁一鲁一区二区张丽| 亚洲精品福利在线| 日韩免费不卡av| 97碰碰碰免费色视频|