Js如何使用ffmpeg進(jìn)行視頻剪輯和畫(huà)面截取等功能
ffmpeg
使用場(chǎng)景是需要在web端進(jìn)行視頻的裁剪,包括使用 在線視頻url 或 本地視頻文件 的裁剪,以及對(duì)視頻內(nèi)容的截取等功能。
前端進(jìn)行視頻操作可能會(huì)導(dǎo)致性能下降,最好通過(guò)后端使用java,c++進(jìn)行處理,本文的案例是備選方案。
注意:
以下所有的使用案例均基于vue3 setup。
同時(shí)由于@ffmpeg版本不同會(huì)導(dǎo)致使用的api不同,使用案例前需要注意@ffmpeg版本問(wèn)題。
如果使用的是0.12+需要使用新的api,詳情請(qǐng)看 文檔
npm
npm install @ffmpeg/ffmpeg@^0.10.0 npm install @ffmpeg/core@^0.10.0
在線視頻url剪輯
<script setup> // "@ffmpeg/core": "^0.10.0", // "@ffmpeg/ffmpeg": "^0.10.0", import { ref, onMounted, onUnmounted } from 'vue' import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg'; const ffmpeg = createFFmpeg({ log: true }); const fileType = ref("") // 視頻文件類型 /** * 根據(jù)在線的視頻地址截取片段 * @param {string} url 在線視頻鏈接 * @param {number|string} startTime 截取開(kāi)始時(shí)間 * @param {number|string} endTime 截取結(jié)束時(shí)間 * @param {Function} callBack 回調(diào)函數(shù) */ const videoCut = async (url, startTime, endTime, callBack) => { if (!ffmpeg.isLoaded()) { await ffmpeg.load(); } if(!url) return; fileType.value = url.split(".").pop() const inputName = `input.${fileType.value}`; const outputName = `output.${fileType.value}`; // 將輸入文件保存到虛擬文件系統(tǒng) await ffmpeg.FS('writeFile', inputName, await fetchFile(url)); // 運(yùn)行 FFmpeg 命令 try { await ffmpeg.run( '-ss', `${startTime}`, '-t', `${endTime - startTime}`, '-i', inputName, '-vcodec', 'copy', '-acodec', 'copy', outputName ); // 讀取輸出文件 let arrayBuffer = ffmpeg.FS('readFile', outputName).buffer; // 讀取緩存 // 創(chuàng)建下載鏈接并通過(guò)回調(diào)下載保存到本地 const fileUrl = URL.createObjectURL(new Blob([arrayBuffer])); // 轉(zhuǎn)為Blob URL callBack && callBack(fileUrl) // 釋放內(nèi)存 ffmpeg.FS('unlink', inputName); ffmpeg.FS('unlink', outputName); } catch (e) { } } const downloadFile = (url, fileName = `clip.${fileType.value}`) => { const link = document.createElement('a'); link.href = url; link.download = fileName; link.click(); } onMounted(() => { videoCut("https://視頻.mp4", 0, 3, downloadFile) }) onUnmounted(() => { ffmpeg.exit(); }) </script>
本地視頻文件剪輯
<template> <input type="file" @change="fileChange"> </template> <script setup> import { ref, onUnmounted } from 'vue' import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg'; const ffmpeg = createFFmpeg({ log: true }); const fileType = ref("") // 視頻文件類型 const fileChange = (e) => { if (!e.target.files[0]) return; const file = e.target.files[0]; fileType.value = file.name.split(".").pop() videoCut(file, 0, 3, downloadFile) } /** * 根據(jù)選擇的視頻文件截取片段 * @param {file} file 選擇的視頻文件 * @param {number|string} startTime 截取開(kāi)始時(shí)間 * @param {number|string} endTime 截取結(jié)束時(shí)間 * @param {Function} callBack 回調(diào)函數(shù) */ const videoCut = async (file, startTime, endTime, callBack) => { if (!ffmpeg.isLoaded()) { await ffmpeg.load(); } if(!file) return; const inputName = `input.${fileType.value}`; const outputName = `output.${fileType.value}`; const orgFileBuffer = await file.arrayBuffer() // 將輸入文件保存到虛擬文件系統(tǒng) await ffmpeg.FS('writeFile', inputName, await fetchFile(new Blob([orgFileBuffer]))); try { await ffmpeg.run( '-ss', `${startTime}`, '-t', `${endTime - startTime}`, '-i', inputName, '-vcodec', 'copy', '-acodec', 'copy', outputName ); // 讀取輸出文件 let arrayBuffer = ffmpeg.FS('readFile', outputName).buffer; // 讀取緩存 // 創(chuàng)建下載鏈接并通過(guò)回調(diào)下載保存到本地 const fileUrl = URL.createObjectURL(new Blob([arrayBuffer])); // 轉(zhuǎn)為Blob URL callBack && callBack(fileUrl) // 釋放內(nèi)存 ffmpeg.FS('unlink', inputName); ffmpeg.FS('unlink', outputName); } catch (e) {} } const downloadFile = (url, fileName = `clip.${fileType.value}`) => { const link = document.createElement('a'); link.href = url; link.download = fileName; link.click(); } onUnmounted(() => { ffmpeg.exit(); }) </script>
獲取視頻畫(huà)面截圖
<template> <input type="file" @change="fileChange"> </template> <script setup> import { ref, onUnmounted } from 'vue' import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg'; const ffmpeg = createFFmpeg({ log: true }); const fileType = ref("") // 視頻文件類型 const fileChange = (e) => { if (!e.target.files[0]) return; const file = e.target.files[0]; fileType.value = file.name.split(".").pop() // 由于這里一秒截取一幀 ,截取5次, 所以如果視頻不足5秒會(huì)導(dǎo)致截取和讀取失敗 // 回調(diào)中是base64圖片組成的數(shù)組,需要在前面拼接 "data:image/png;base64," ,然后在img的src中賦值即可 videoFrame(file, 5, 1, (data) => console.log(data)) } /** * 根據(jù)選擇的視頻文件獲取視頻截圖 * @param {file} file 選擇的視頻文件 * @param {number|string} count 截取圖片的次數(shù) * @param {number|string} interval 截取圖片的間隔 * @param {Function} callBack 回調(diào) */ const videoFrame = async (file, count, interval, callBack) => { if (!ffmpeg.isLoaded()) { await ffmpeg.load(); } if(!file) return; const inputName = `input.${fileType.value}`; const orgFileBuffer = await file.arrayBuffer() // 將輸入文件保存到虛擬文件系統(tǒng) await ffmpeg.FS('writeFile', inputName, await fetchFile(new Blob([orgFileBuffer]))); try { await ffmpeg.run( "-i", inputName, "-r", `${interval}`, "-ss", "0", "-vframes", `${count}`, "-f", "image2", "-s", "88*50", "image-%02d.png" ); const baseArr = [] for (let i = 0; i < count; i++) { let temp = i + 1; if (temp < 10) { temp = "0" + temp; } baseArr.push( arrayBufferToBase64(ffmpeg.FS("readFile", "image-" + temp + ".png")) ); } callBack && callBack(baseArr) // 釋放內(nèi)存 ffmpeg.FS('unlink', inputName); } catch (e) {} } const arrayBufferToBase64 = (array) => { array = new Uint8Array(array); var length = array.byteLength; var table = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/']; var base64Str = ""; for (var i = 0; length - i >= 3; i += 3) { var num1 = array[i]; var num2 = array[i + 1]; var num3 = array[i + 2]; base64Str += table[num1 >>> 2] + table[((num1 & 0b11) << 4) | (num2 >>> 4)] + table[((num2 & 0b1111) << 2) | (num3 >>> 6)] + table[num3 & 0b111111]; } var lastByte = length - i; if (lastByte === 1) { var lastNum1 = array[i]; base64Str += table[lastNum1 >>> 2] + table[(lastNum1 & 0b11) << 4] + "=="; } else if (lastByte === 2) { // eslint-disable-next-line no-redeclare var lastNum1 = array[i]; var lastNum2 = array[i + 1]; base64Str += table[lastNum1 >>> 2] + table[((lastNum1 & 0b11) << 4) | (lastNum2 >>> 4)] + table[(lastNum2 & 0b1111) << 2] + "="; } return base64Str } onUnmounted(() => { ffmpeg.exit(); }) </script>
總結(jié)
到此這篇關(guān)于Js如何使用ffmpeg進(jìn)行視頻剪輯和畫(huà)面截取等功能的文章就介紹到這了,更多相關(guān)Js ffmpeg視頻剪輯和畫(huà)面截取內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js實(shí)現(xiàn)簡(jiǎn)單的隨機(jī)點(diǎn)名器
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)簡(jiǎn)單的隨機(jī)點(diǎn)名器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-09-09前端實(shí)現(xiàn)跨頁(yè)面通信的最全實(shí)現(xiàn)方案指南
這篇文章將從同源頁(yè)面,不同源頁(yè)面,父子框架三個(gè)維度,詳細(xì)為大家講解前端跨頁(yè)面通信的各種實(shí)現(xiàn)方案,并提供代碼示例和對(duì)比分析,需要的小伙伴可以參考下2025-04-04JS原生數(shù)據(jù)雙向綁定實(shí)現(xiàn)代碼
本文通過(guò)實(shí)例代碼給大家介紹了JS原生數(shù)據(jù)雙向綁定問(wèn)題,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下吧2017-08-08js中document.referrer實(shí)現(xiàn)移動(dòng)端返回上一頁(yè)
本文主要介紹了document.referrer實(shí)現(xiàn)移動(dòng)端返回上一頁(yè)的方法,具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-02-02JS實(shí)現(xiàn)掃雷項(xiàng)目總結(jié)
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)掃雷項(xiàng)目總結(jié),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05基于代數(shù)方程庫(kù)Algebra.js解二元一次方程功能示例
這篇文章主要介紹了基于代數(shù)方程庫(kù)Algebra.js解二元一次方程功能,結(jié)合具體實(shí)例形式分析了方程庫(kù)Algebra.js計(jì)算方程的具體使用技巧,需要的朋友可以參考下2017-06-06Web程序員必備的7個(gè)JavaScript函數(shù)
這篇文章主要為大家詳細(xì)介紹了Web程序員必備的7個(gè)JavaScript函數(shù),教會(huì)大家如何靈活運(yùn)用JavaScript函數(shù),感興趣的小伙伴們可以參考一下2016-06-06基于Bootstrap 3 JQuery及RegExp的表單驗(yàn)證功能
這篇文章主要介紹了基于Bootstrap 3 JQuery及RegExp的表單驗(yàn)證功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-02-02