ffmpeg的使用和学习
vue中使用ffmpeg给图片和视频加水印
·
最近有个项目有关于视频加水印、裁剪、压缩的需求,然后经过调研发现了ffmpeg这个插件,其实在写demo之前我看到了很多说它使用起来很慢的帖子,经过使用才知道确实挺慢的。
1、首先装相应的两个ffmpeg的插件:
npm install @ffmpeg/ffmpeg @ffmpeg/core
2、在文件中引入插件并且启用:
//正常情况下可以直接引用插件的方式,但是因为我这边使用的是qiankun的微前端,所以直接引用插件拿不到的createFFmpegCore,所以这边改了点代码,就用这样的方式引用了
import { createFFmpeg, fetchFile } from '../@ffmpeg/ffmpeg/src/index.js';
// 加载外部核心文件
// console.log(location.origin);
let ffmpeg = createFFmpeg({
corePath: 'http://localhost:12216/ffmpeg-core.js', //我将ffmpeg-core文件放在了public静态文件下,所以在本地使用的这种方法引入
// log: true, //是否显示log
});
(async () => {
await ffmpeg.load();
})();
3、配置代理:
我这边是微前端,是配置在主服务的
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
},
目前为止,ffmpeg使用前的配置就已经完成,接下来就是一些相关的使用,以下是我粗浅的理解写的一些测试代码:
<template>
<div class="file-upload">
<div class="uploadButton">
<input id="upload"
ref="file"
type="file"
style="display: none"
@change="uploadFile()">
<div v-loading="isLoading"
:element-loading-text="loadingText"
class="upload-file-box"
@click="goFile">
<i class="icon-tianjia1"
style="color:rgba(25, 140, 255, 1)"></i>
<div class="text">上传图片/视频</div>
</div>
</div>
<div class="uploadInfo">
<div style="font-size: 8px;margin-right: 10px;width: 100%;">{{ fileInfo.fileName }}</div>
<el-row v-if="statusShow"
type="flex">
<el-progress class="progress-1"
:percentage="schedule"
:stroke-width="4"
style="width:300px;line-height: 2;" />
<span v-show="schedule === 100"
style="font-size: 8px;padding: 0 10px 0 20px;color: #31cd31;">上传成功!</span>
</el-row>
</div>
</div>
</template>
<script>
import { createFFmpeg, fetchFile } from '../@ffmpeg/ffmpeg/src/index.js';
import SparkMD5 from 'spark-md5';
// import { filechunk, filecompose, uploadfileList, fetchFilePicture } from '../../api/file';
// 加载外部核心文件
// console.log(location.origin);
let ffmpeg = createFFmpeg({
corePath: 'http://localhost:12216/ffmpeg-core.js',
// log: true, //是否显示log
});
(async () => {
await ffmpeg.load();
})();
export default {
name: 'DynamicUpload',
data() {
return {
fileInfo: {
fileName: '',
fileMd5: '',
chunkCount: 0
},
chunkSize: 200 * 1024 * 1024,
schedule: 0,
statusShow: false,
minDuration: 20, //最小时间
maxDuration: 80, //最大时间
//视频处理的loading
isLoading: false,
loadingText: '加载中...'
};
},
mounted() {
},
methods: {
// 按钮跳转
goFile() {
this.$refs.file.click();
},
async uploadFile() {
// 获取用户选择的文件
const file = document.getElementById('upload').files[0];
if (file === undefined) {
this.$message.error('没有选中文件');
return;
}
// 文件类型判断是视频还是图片
let videoTypeList = ["mp4", 'webm', 'ogg'];
const newArr = file.name.split('.');
if (videoTypeList.includes(newArr[newArr.length - 1])) {
// 对上传视频做处理
let orgFileBuffer = await file.arrayBuffer(); // 获取文件数据
ffmpeg.FS('writeFile', file.name, await fetchFile(new Blob([orgFileBuffer]))); // 将视频数据写入内存
//获取视频时长 ------start
var duration;
let getDuration = new Promise(resolve => {
var url = URL.createObjectURL(file);
var audioElement = new Audio(url);
audioElement.addEventListener("loadedmetadata", function (_event) {
duration = audioElement.duration; //时长为秒,小数,182.36
if (duration) {
resolve();
}
});
});
// ---------end
getDuration.then(() => {
console.log(duration);
if (duration < this.minDuration) {
this.$message.error('不能上传小于' + this.minDuration + '秒的视频,请重新上传!');
return;
}
if (duration > this.maxDuration) {
//对长视频进行裁剪
this.splitVideo(file);
this.$message.warning('上传的视频超过最大时长限制,将会截前' + this.maxDuration + '秒钟的视频上传!');
}
this.formatVideo(file);//对视频做处理,结束之后上传
});
} else {
//对上传图片做处理
let orgFileBuffer = await file.arrayBuffer(); // 获取文件数据
ffmpeg.FS('writeFile', file.name, await fetchFile(new Blob([orgFileBuffer]))); // 将视频数据写入内存
this.formatImage(file);
}
},
//视频剪切
async splitVideo(file) {
this.isLoading = true;
this.loadingText = '视频截取中...';
let start = 0; // 视频开始截取的时间
const beginTime = start * 0.001.toFixed(3) + ''; //开始时间
const endTime = (start + this.maxDuration) + ''; // 结束时间
const lineTime = ['-ss', beginTime, '-to', endTime]; // 切剪的时间线的begin/end
const newFile = ['-i', file.name]; //输入的文件名称
// 纯视频剪切(不论大小,1s完成)
let test = ['-c', 'copy']; // 原视频输出,不做解码
const end = [file.name]; //输出的文件名称
await ffmpeg.run(...newFile, ...lineTime, ...test, ...end);
let splitFile = this.handleTranslateType(file.name, file.name, 'video/mp4'); //经过处理之后的blob文件
console.log(splitFile);
// 下载到本地查看(调试时使用)
// this.fileDownloadLocal(outputImgName);
this.isLoading = false;
},
//视频处理
async formatVideo(file) {
this.isLoading = true;
this.loadingText = '准备上传...';
// 截取一帧图片作为封面
let splitTime = '00:00:00';//截取时间
let outputImgName = 'cover.png';
await ffmpeg.run('-ss', splitTime, '-i', file.name, '-f', 'image2', '-r', '1', '-vframes', '14', '-s', '1920*1080', outputImgName);
let coverPng = this.handleTranslateType(outputImgName, outputImgName, 'png'); //blob格式的封面图片
console.log(coverPng);
// 下载到本地查看(调试时使用)
// this.fileDownloadLocal(outputImgName);
let img = 'http://localhost:12216/favicon.ico'; //水印的图片
ffmpeg.FS('writeFile', 'overlay.png', await fetchFile(img)); //将水印图片写入内存
let fontFile = 'http://localhost:12216/UniTortred.ttf';
ffmpeg.FS('writeFile', 'UniTortred.ttf', await fetchFile(fontFile)); //将水印图片写入内存
let waterMark = ['-vf', "movie=overlay.png[watermark];[in][watermark] overlay=main_w-overlay_w-10:main_h-overlay_h-10[out]"];//水印相关参数
// let waterMark = ['-vf', "drawtext=fontsize=100:fontfile=UniTortred.ttf:text='hello world':x=100:y=100:fontcolor=green:box=1:boxcolor=yellow"];//水印相关参数
let fast = ['-preset', 'ultrafast']; //解码速率,与视频质量成反比 ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow
let threads = ['-threads', '6']; //进程控制
let zipFile = ["-b", "2000000",]; //压缩
let other = ['-vcodec', 'libx264', '-r', '10']; //指定编译器和帧率调整
const resolvingPower = ['-vf', 'scale=640:-1']; //视频输出的像素
// 相关参数,可自行调整
//'流畅360P', // '640:-1': '360P'
//'标清480P', // '720:-1': '480P',
//'高清720P', // '1280:-1'
//'超清1080P', // '1920:-1'
//'': '原视频输出';
const newFile = ['-i', file.name]; //输入的文件名称
const end = [file.name]; //输出的文件名称
// 不建议使用,因为对大视频处理速度极慢,暂未找到优化的方法
await ffmpeg.run(
...newFile,
...other,
...zipFile,
...threads,
...fast,
...waterMark,
...resolvingPower,
...end
);
let afterFile = this.handleTranslateType(file.name, file.name, 'video/mp4'); //经过处理之后的blob文件
console.log(afterFile);
// 下载到本地查看(调试时使用)
this.fileDownloadLocal(file.name);
// 得到处理的视频之后上传
this.isLoading = false;
this.functionUploadFile(afterFile);
},
//图片处理
async formatImage(file) {
// 给图片加水印
this.isLoading = true;
this.loadingText = '准备上传...';
let img = 'http://localhost:12216/favicon.ico'; //水印的图片
ffmpeg.FS('writeFile', 'overlay.png', await fetchFile(img)); //将水印图片写入内存
let fontFile = 'http://localhost:12216/UniTortred.ttf';
ffmpeg.FS('writeFile', 'UniTortred.ttf', await fetchFile(fontFile)); //将水印图片写入内存
let waterMark = ['-vf', "movie=overlay.png[watermark];[in][watermark] overlay=main_w-overlay_w-10:main_h-overlay_h-10[out]"];//水印相关参数
//文字水印,需要相关配置,还在调研
// let waterMark = ['-vf', "drawtext=fontsize=100:fontfile=UniTortred.ttf:text='hello world':x=100:y=100:fontcolor=green:box=1:boxcolor=yellow"];
let zipFile = ["-b", "2000000",]; //压缩
const newFile = ['-i', file.name]; //输入的文件名称
const end = [file.name]; //输出的文件名称
// 图片处理比较快
await ffmpeg.run(
...newFile,
...zipFile,
...waterMark,
...end
);
let afterFile = this.handleTranslateType(file.name, file.name, 'jpg'); //经过处理之后的blob文件
// console.log(afterFile);
// 下载到本地查看(调试时使用)
// this.fileDownloadLocal(file.name);
this.isLoading = false;
this.functionUploadFile(afterFile);
},
async functionUploadFile(file) {
this.$emit("changFileLoaing", true);
if (file.size > 0) {
this.statusShow = true;
this.fileInfo.fileName = file.name;
this.fileInfo.chunkCount = Math.ceil(file.size / this.chunkSize);
let filemd5length = 1024 * 1024 * 10;
if (file.size < 1024 * 1024 * 2) {
filemd5length = file.size;
}
let filemd5 = file.slice(0, filemd5length);
this.fileInfo.fileMd5 = await this.getFileMd5(filemd5);
}
// 分片上传
// filechunk(this.fileInfo).then(
// async res => {
// if (res.code == 0) {
// // 分片上传完成 待合并文件
// if (res.data.uploadCode == 3) {
// this.composeFile(this.fileInfo);
// return;
// }
// if (res.data.uploadCode == 1) {
// this.$emit("changFileLoaing", false); //父頁面loding图标
// this.$emit("setattachmentIds", res.data.id, this.fileInfo.fileName); //父页面传参
// this.schedule = 100;
// this.$message.success('文件上传成功');
// return;
// }
// const chunkUploadUrls = res.data.chunkUploadUrls;
// let num = 0;//把上一个片段的进度存起来
// for (const item of chunkUploadUrls) {
// // 分片开始位置
// const start = (item.partNumber - 1) * this.chunkSize;
// // 分片结束位置
// const end = Math.min(file.size, start + this.chunkSize);
// // 取文件指定范围内的byte,从而得到分片数据
// const chunkFile = file.slice(start, end);
// const config = {
// onUploadProgress: (e) => {
// let count = ((e.loaded / e.total) * 100) / this.fileInfo.chunkCount;
// if (file.size > 1024 * 1024 * 1024 * 2) { //文件大于2G
// this.schedule = num + parseInt(count);
// } else {
// this.schedule = num + parseInt(count / 2);
// }
// }
// };
// await uploadfileList(item.uploadUrl, chunkFile, config);
// num = this.schedule;
// }
// this.composeFile(this.fileInfo);
// } else {
// this.$message.error("文件上传失败");
// this.$emit("changFileLoaing", false);
// }
// }
// );
},
//MD5加密,为了分段上传文件
async getFileMd5(file) {
const fileReader = new FileReader();
fileReader.readAsBinaryString(file);
const spark = new SparkMD5();
return new Promise((resolve) => {
fileReader.onload = (e) => {
spark.appendBinary(e.target.result);
resolve(spark.end());
};
});
},
// 将文件转化为blob格式
handleTranslateType(inputFileName, outputFileName, outputFileType) {
const afterFile = new window.File(
[new Blob([ffmpeg.FS('readFile', inputFileName)])],
outputFileName,
{ type: outputFileType }
);
return afterFile;
},
// 通过文件名将MEMFS文件保存到本地
fileDownloadLocal(filename) {
const mime = 'application/octet-stream';
const content = ffmpeg.FS('readFile', filename);
// console.log(`Offering download of "${filename}", with ${content.length} bytes...`)
var a = document.createElement('a');
a.download = filename;
a.href = URL.createObjectURL(new Blob([content], { type: mime }));
a.style.display = 'none';
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(a.href);
}, 2000);
ffmpeg.FS('unlink', filename);
},
}
};
</script>
<style scoped lang="scss">
.file-upload {
.upload-file-box {
width: 200px;
height: 150px;
border-radius: 4px;
background: rgba(245, 247, 250, 1);
border: 1px dashed rgba(25, 140, 255, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.text {
font-size: 12px;
font-weight: 400;
line-height: 28px;
color: rgba(153, 153, 153, 1);
}
}
}
</style>
总结:ffmpeg是一个非常强大的插件,我的理解还停留在最浅显的层面,我尝试过优化加快它的转码速度,但是都不能成功,这种写法,上传一个800M的4k视频,转码时间长达10分钟,非常漫长,所以最后还是没能使用在项目中,希望有大神看到这篇文章可以给我点意见,非常感谢!
更多推荐
已为社区贡献1条内容
所有评论(0)