package.json
"dependencies": {
"@ffmpeg/ffmpeg": "^0.12.10",
"@ffmpeg/util": "^0.12.1"
}
vue3组件代码
根据需要更改
html
<script setup lang="ts">
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile, toBlobURL } from '@ffmpeg/util';
import useInject from '@/utils/useInject';
import { reactive, onUnmounted } from 'vue';
const { $global, $fn } = useInject();
const props: any = defineProps<{
params?: any;
url?: any;
}>();
let ffmpegObj: any = reactive({
file: null,
urlpre: null,
url: null,
params: {
...(props?.params || {}),
ext: '',
starttime: 0,
endtime: 3,
maxtime: 0,
},
});
const ffmpeg = new FFmpeg();
function filechange(e: any) {
ffmpegObj.file = e.target.files[0];
// 原视频预览方案一
const reader: any = new FileReader();
reader.readAsDataURL(ffmpegObj.file);
reader.onload = () => {
ffmpegObj.url = '';
ffmpegObj.urlpre = reader.result;
const audioElement: any = new Audio(ffmpegObj.urlpre);
audioElement.addEventListener('loadedmetadata', () => {
ffmpegObj.params.maxtime = audioElement.duration;
ffmpegObj.params.starttime = 0;
ffmpegObj.params.endtime = audioElement.duration;
});
};
// 原视频预览方案二
// const reader: any = new FileReader();
// reader.readAsArrayBuffer(ffmpegObj.file);
// let blob = null;
// reader.onload = (e: any) => {
// if (typeof e.target.result === 'object') {
// blob = new Blob([e.target.result]);
// } else {
// blob = e.target.result;
// }
// ffmpegObj.url = '';
// if (blob == null) return;
// const url = URL.createObjectURL(blob);
// ffmpegObj.urlpre = url;
// const audioElement = new Audio(url);
// audioElement.addEventListener('loadedmetadata', function () {
// ffmpegObj.params.maxtime = audioElement.duration;
// ffmpegObj.params.starttime = 0;
// ffmpegObj.params.endtime = audioElement.duration;
// });
// };
}
async function doffmpeg() {
try {
if (!ffmpegObj.file) {
return;
}
ffmpegObj.fileext = ffmpegObj.file.name.split('.').pop();
const inputName = `input.${ffmpegObj.fileext}`;
const outputName = `output.${
ffmpegObj.params.ext ? ffmpegObj.params.ext : ffmpegObj.fileext
}`;
console.log(666.001, ffmpeg, ffmpegObj);
ffmpeg.on('log', ({ message: msg }) => {
ffmpegObj.msglogger = msg;
});
await ffmpeg.load({
coreURL: await toBlobURL(
'./ffmpeg/ffmpeg-core.js',
'text/javascript'
),
wasmURL: await toBlobURL(
'./ffmpeg/ffmpeg-core.wasm',
'application/wasm'
),
workerURL: await toBlobURL(
`./ffmpeg/ffmpeg-core.worker.js`,
'text/javascript'
),
});
await ffmpeg.writeFile(inputName, await fetchFile(ffmpegObj.urlpre));
await ffmpeg.exec([
'-ss',
`${
ffmpegObj.params.starttime !== undefined
? ffmpegObj.params.starttime
: 0
}`,
'-t',
`${
(ffmpegObj.params.endtime !== undefined
? ffmpegObj.params.endtime
: 3) -
(ffmpegObj.params.starttime !== undefined
? ffmpegObj.params.starttime
: 0)
}`,
'-i',
inputName,
outputName,
]);
ffmpegObj.data = await ffmpeg.readFile(outputName);
ffmpegObj.blob = new Blob([ffmpegObj.data.buffer], {
type:
'video/' + ffmpegObj.params.ext
? ffmpegObj.params.ext
: ffmpegObj.fileext,
});
ffmpegObj.url = URL.createObjectURL(ffmpegObj.blob);
ffmpegObj.urlpre = ''; // 清空原预览视频
} catch (err) {
alert('出错了');
throw err;
}
}
function blobToBase64(blob: any) {
console.log(666.3002, blob);
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.readAsDataURL(blob);
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.onerror = () => {
reject(new Error('文件流异常'));
};
});
}
// function convertBlobToFile(blob, fileName) {
// blob.lastModifiedDate = new Date();
// blob.name = fileName;
// return blob;
// }
async function submit() {
await doffmpeg();
const resdata: any = await blobToBase64(ffmpegObj.blob);
// const resdata: any = convertBlobToFile(ffmpegObj.blob, 'aaa');
console.log(666.30002, resdata);
$fn.useApiFiles()
.request({
data: props.url
? { url: props.url }
: {
dir: 'chat/chat',
uid: $fn.As.Uuid('CU'),
ext:
'.' +
(ffmpegObj.params.ext
? ffmpegObj.params.ext
: ffmpegObj.fileext),
data: resdata,
},
})
.then((res: any) => {
console.log(666.789, res);
props?.params?.fn(res);
})
.catch((err: any) => {
$global.tipsfn({ type: 'err', con: err });
});
}
//下载
function downffmpeg() {
const oA: any = document.createElement('a');
oA.download =
'video_' +
Date.now() +
'.' +
(ffmpegObj.params.ext ? ffmpegObj.params.ext : ffmpegObj.fileext);
oA.href = ffmpegObj.url;
document.body.appendChild(oA);
oA.click();
// 释放 URL 对象
URL.revokeObjectURL(oA.href);
oA.remove();
}
onUnmounted(() => {
ffmpegObj = null;
});
</script>
<template>
<div class="as-ff-area">
<div class="as-show-area">
<!-- <template v-if="ffmpegObj.urlpre"> -->
原视频
<video :src="ffmpegObj.urlpre" controls></video>
<!-- </template> -->
<!-- <template v-else> -->
裁剪后视频
<video :src="ffmpegObj.url" controls></video>
<!-- </template> -->
日志:
{{ ffmpegObj.params.starttime }}
-
{{ ffmpegObj.params.endtime }}
({{ ffmpegObj.params.maxtime }})
<input
type="range"
v-model="ffmpegObj.params.starttime"
min="0"
:max="ffmpegObj.params.maxtime"
/>
<input
type="range"
v-model="ffmpegObj.params.endtime"
min="0"
:max="ffmpegObj.params.maxtime"
/>
<!-- <input type="number" v-model="ffmpegObj.params.starttime" />
<input type="number" v-model="ffmpegObj.params.endtime" /> -->
{{ ffmpegObj.msglogger }}
{{ ffmpegObj.msgprogress }}
</div>
<div class="as-file-area">
选择图片:
<input class="file" type="file" @change="filechange" />
</div>
<div class="as-btn-area">
<button @click="doffmpeg()">裁剪</button>
<button @click="downffmpeg()">下载</button>
<button @click="submit()">提交</button>
</div>
</div>
</template>
<style scoped>
.as-ff-area {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.as-btn-area {
text-align: center;
height: auto;
}
.as-btn-area > button {
user-select: none;
padding: 5px 12px;
margin: 15px 5px 0 5px;
}
.as-file-area {
padding: 5px 12px;
text-align: center;
height: auto;
width: auto;
display: inline-block;
background-color: var(--ch1);
}
.as-show-area {
flex-grow: 1;
width: 100%;
overflow: auto;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
</style>