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运行命令:
install -i
或直接运行命令安装ffmpeg:
npm install @ffmpeg/ffmpeg @ffmpeg/core -S;
安装后package-lock.json会自动写入如下配置:
1.2.1 报错处理
如果出现安装问题:
①先在npm -i命令后加--legacy-peer-deps 或者 --force运行
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插件目录下找到:
复制到项目代码的public目录里
1.3.1 ffmpeg压缩参数配置
-b:指定视频比特率
-crf:恒定速率因子,控制输出视频质量的参数。
这个参数的取值范围为0~51,其中0为无损模式。数值越大,画质越差,生成的文件却越小。
从主观上讲,18~28是一个合理的范围。18被认为是视觉无损的(从技术角度上看当然还是有损的),它的输出视频质量和输入视频相当。
-fs:压缩到指定大小,单位Byte
-s:分辨率
控制压缩后视频质量的最重要的是后面三个参数:crf、fs、s
1.4 项目里引用并封装ffmpeg
在util目录下封装ffmpeg.js以便项目全局引用
封装的工具通过:
import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg'
把ffmpeg插件引入到项目里使用。
完整ffmpeg.js代码:
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 ffmpeg = createFFmpeg({
// ffmpeg路径
corePath: 'ffmpeg-core.js',
// 日志
log: true,
// 进度
progress: ({ ratio }) => {
msg = `完成率: ${(ratio * 100.0).toFixed(1)}%`
}
})
var { name } = file
// msg = '正在加载 ffmpeg-core.js'
// 开始加载
await ffmpeg.load()
// msg = '开始压缩'
// 把文件加到ffmpeg 写文件
ffmpeg.FS('writeFile', name, await fetchFile(file))
// await ffmpeg.run('-i', name, '-b', '2000000', '-fs', '4194304', '-preset medium', 'superfast', 'output.mp4')
// 开始压缩视频
await ffmpeg.run('-i', name, '-b', '2000000', '-crf', '18', '-fs', '31457280', '-s', resolution, 'output.mp4')
// msg = '压缩完成'
// 压缩所完成, 读文件 压缩后的文件名称为 output.mp4
const data = ffmpeg.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)
})
}
// 获取上传视频的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.视频上传元素
<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>
3.上传压缩脚本
<script>
import ffmpeg from "@/utils/ffmpeg";
export default {
data() {
return {
msg: '',
videoWidth: '',
videoHeight: '',
duration: '',
actionUrl: ''
}
},
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>
注意异步处理:异步压缩,再上传
可使用Promise
4.其他配置:
4.1 vue项目根目录下的vue.config.js里加配置
headers: { 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp' }
module.exports = {
publicPath: './',
devServer: {
client: {
overlay: false,
},
port: 9002,
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp'
}
},
transpileDependencies: []
}
以免出现如下SharedArrayBuffer的报错:
5.其他实现方案
插件video-conversion.js