记录---前端Vue使用ffmpeg压缩视频再上传
🧑💻 写在开头
点赞 + 收藏 === 学会🤣🤣🤣
保姆级操作步骤,从我实际运行中的项目中摘取的所有相关代码展示如下:
1.Vue项目中安装插件ffmpeg
1.1 插件版本依赖配置
两个插件的版本 "@ffmpeg/core": "^0.10.0", "@ffmpeg/ffmpeg": "^0.10.1"
package.json 和 package-lock.json 都加入如下ffmpeg的版本配置:
1.2 把ffmpeg安装(下载)到项目依赖目录里
terminal运行命令:
1 | npm i |
或直接运行命令安装ffmpeg:
1 | npm install @ffmpeg/ffmpeg @ffmpeg/core -S |
安装后package-lock.json会自动写入如下配置:
1.2.1 报错处理
如果出现安装问题:
①先在npm i命令后加--legacy-peer-deps 或者 --force运行
1 | npm i --force |
②如果上步不行,尝试删除这个安装依赖目录node_modules
和package-lock.json文件,重试npm i
请参考:
npm ERR! code ERESOLVEnpm ERR! ERESOLVE could not resolve 报错,版本冲突,最全解决步骤(#^.^#)_npm err! code eresolve npm err! eresolve could not-CSDN博客
1.2.2 镜像过期
安装ffmpeg可能提示镜像证书过期
你使用的镜像地址可能还是这个过期的淘宝镜像:https://registry.npm.taobao.org/
按如下步骤重设镜像地址:
①查看镜像:npm config list
②强制清理镜像缓存:npm cache clean --force
③设置镜像:npm config set registry https://registry.npmmirror.com/(国内推荐淘宝新镜像)
也可:npm config set registry https://registry.npmjs.org/
1.3 把ffmpeg安装到项目里
在项目里的ffmpeg插件目录下找到:
ffmpeg-core.js
ffmpeg-core.wasm
ffmpeg-core-worker.js
复制到项目代码的public目录里
至此,项目里安装ffmpeg完毕。
接下来就是在代码块里使用ffmpeg
2 项目里引用并封装ffmpeg
在util目录下封装ffmpeg.js以便项目全局引用
封装的工具通过:
1 | import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg' ; |
把ffmpeg插件引入到项目里使用。
完整ffmpeg.js代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg' ; let ffmpeg = {}; ffmpeg.squeezVideo = async function(file, filename, filetype, width, height, msg) { console.log( 'file' , file); console.log( 'filename' , filename); console.log( 'filetype' , filetype); console.log( 'width' , width); console.log( 'height' , height); // 分辨率 const resolution = `${width}x${height}`; // 实例化ffmpeg const ffmpegObj = createFFmpeg({ // ffmpeg路径 corePath: 'ffmpeg-core.js' , // 日志 log: true , // 进度 progress: ({ ratio }) => { msg = `完成率: ${(ratio * 100.0).toFixed(1)}%`; } }) var { name } = file; // msg = '正在加载 ffmpeg-core.js' // 开始加载 await ffmpegObj.load(); // msg = '开始压缩' // 把文件加到ffmpeg 写文件 ffmpegObj.FS( 'writeFile' , name, await fetchFile(file)); // await ffmpeg.run('-i', name, '-b', '2000000', '-fs', '4194304', '-preset medium', 'superfast', 'output.mp4') // 开始压缩视频 const compressedFileSize = this .computeFileSize(file); console.log( "After compression,this file size is " + compressedFileSize + " Bytes." ); await ffmpegObj.run( '-i' , name, '-b' , '2000000' , '-crf' , '18' , '-fs' , compressedFileSize, '-s' , resolution, 'output.mp4' ); // msg = '压缩完成' // 压缩所完成, 读文件 压缩后的文件名称为 output.mp4 const data = ffmpegObj.FS( 'readFile' , 'output.mp4' ); // 转换bolb类型 const blob = new Blob([data], { type: 'text/plain;charset=utf-8' }); return new Promise((resolve, reject) => { const file = new window.File([blob], filename, { type: filetype }); resolve(file); }) } ffmpeg.computeFileSize = function(file) { if (!file){ return '0' ; } if (file.size / 1024 / 1024 > 60){ //30M return '31457280' ; } else if (file.size / 1024 / 1024 <= 60 && file.size / 1024 / 1024 > 30){ return file.size / 2; } else { return file.size; } } // 获取上传视频的url ffmpeg.getObjectURL = function(file) { let url = null ; window.URL = window.URL || window.webkitURL; if (window.URL) { url = window.URL.createObjectURL(file); } else { url = URL.createObjectURL(file); } return url; } // 获取视频的宽高分辨率 ffmpeg.getVideoData = function() { return new Promise((resolve, reject) => { const videoElement = document.getElementById( 'video' ); videoElement.addEventListener( 'loadedmetadata' , function () { resolve({ width: this .videoWidth, height: this .videoHeight, duration: this .duration }) }); }) } export default ffmpeg; |
2.1 ffmpeg压缩参数配置
-b:指定视频比特率
-crf:恒定速率因子,控制输出视频质量的参数。
这个参数的取值范围为0~51,其中0为无损模式。数值越大,画质越差,生成的文件却越小。
从主观上讲,18~28是一个合理的范围。18被认为是视觉无损的(从技术角度上看当然还是有损的),它的输出视频质量和输入视频相当。
-fs:压缩到指定大小,单位Byte
-s:分辨率
控制压缩后视频质量的最重要的是后面三个参数:crf、fs、s
3.视频上传元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <template> <el-upload ref = 'operationVideoUpload' :limit= "1" list-type= 'text' : class = "{disabled:addModelParam.attachments.operationVideo.length>0}" :action= 'actionUrl' : on -success= "(res,file)=>handleVideoSuccess(res,file,'operationVideo')" :before-upload= 'beforeAvatarUploadVideo' : on -remove= "(file,fileList)=>handleRemove(file,fileList,'operationVideo')" :auto-upload= 'true' : on -exceed= "handelFileExceed" accept= '.mp4,.mov,.wmv,.flv,.mvi,.mkv' > <el-button style= "position: relative; margin: -5px" ><i class = "el-icon-circle-plus-outline" style= "color: #66b1ff;" >上传附件</i></el-button> <br/><br/> <p>{{ msg }}</p> </el-upload> <video id= "video" hidden controls object -fill= "fill" ></video> </template> |
如果不想要展示压缩视频,可以去掉video标签。
4.上传压缩脚本
把封装的ffmpeg.js导入到页面使用:
import ffmpeg from "@/utils/ffmpeg";
完整js脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | <script> import ffmpeg from "@/utils/ffmpeg" ; export default { data() { return { msg: '' , videoWidth: '' , videoHeight: '' , duration: '' , actionUrl: '' , addModelParam: { attachments: { operationVideo: [] } }, } }, created() { this .actionUrl = "你的后端上传文件接口地址URL" ; }, methods: { handleVideoSuccess(res, file, code) { this .msg = "已完成视频压缩后上传!" ; file.url = res.data.url; file.fileId = res.data.fileId; this .addModelParam.attachments[code].push(file.fileId); }, handleAvatarSuccess(res, file, code) { file.url = res.data.url; file.fileId = res.data.fileId; this .addModelParam.attachments[code].push(file.fileId); }, handleRemove(file, fileList, code) { this .addModelParam.attachments[code].splice( this .addModelParam.attachments[code].indexOf(file.fileId),1) }, beforeAvatarUploadVideo(file) { const isLarge = file.size / 1024 / 1024 > 30; if (isLarge) { this .msg = "请稍等,过大的视频正在压缩上传中..." ; //压缩视频 return this .uploadCompressVideo(file); } }, handelFileExceed(){ this .$message( '文件数量超出限制!' ); }, // 上传视频文件压缩后再上传 uploadCompressVideo(file) { if (file) { let filename = file.name; let filetype = file.type; const videoUrl = ffmpeg.getObjectURL(file); const video = document.getElementById( 'video' ); video.src = videoUrl; return ffmpeg.getVideoData().then((videoObj) => { const {width, height} = videoObj; return ffmpeg.squeezVideo(file, filename, filetype, width, height, this .msg); }) } }, }, } </script> |
注意异步处理:异步压缩,再上传
可使用new Promise();
5.其他配置:
5.1 vue项目根目录下的vue.config.js里加配置
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp'
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | module.exports = { publicPath: './' , devServer: { client: { overlay: false , }, port: 9002, headers: { 'Cross-Origin-Opener-Policy' : 'same-origin' , 'Cross-Origin-Embedder-Policy' : 'require-corp' } }, transpileDependencies: [] } |
以免出现如下SharedArrayBuffer的报错:
6.其他实现方案
插件video-conversion.js
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体