<template>
|
<div>
|
<input type="file" @change="handleFileChange" accept="video/*" />
|
<div> 请上传 <span style="color: red;" >mp4</span> 格式文件 </div>
|
</div>
|
</template>
|
|
<script setup>
|
import { onMounted, ref } from 'vue';
|
import * as bodyPix from '@tensorflow-models/body-pix';
|
import '@tensorflow/tfjs-backend-webgl';
|
import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg';
|
import * as FileApi from '@/api/infra/file'
|
import CryptoJS from 'crypto-js'
|
// import { UploadRawFile, UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
|
import axios from 'axios'
|
|
// 定义响应式变量
|
const net = ref(null);
|
const downloadUrl = ref(null);
|
const ffmpeg = ref(null);
|
const videoElement = ref(null);
|
const canvas = ref(null);
|
const ctx = ref(null);
|
const frameRate = ref(30);
|
const frameDuration = ref(1000 / 30);
|
const segments = ref([]);
|
const getUploadUrl= ref("")
|
// 文件名称
|
const fileName = ref("")
|
// 文件类型
|
const fileType = ref("")
|
const emit = defineEmits(['Thnd','start'])
|
onMounted(async () => {
|
/**
|
* 获得上传 URL
|
*/
|
getUploadUrl.value = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/infra/file/upload';
|
ffmpeg.value = createFFmpeg({ log: true });
|
await ffmpeg.value.load();
|
net.value = await bodyPix.load({
|
architecture: 'MobileNetV1',
|
outputStride: 16,
|
multiplier: 0.5,
|
quantizationBytes: 2
|
});
|
});
|
|
const handleFileChange = (e) => {
|
const file = e.target.files[0];
|
if (file) {
|
emit('start')
|
fileName.value = file.name
|
fileType.value = file.type
|
handleVideoProcessing(file);
|
}
|
};
|
|
const handleVideoProcessing = async (file) => {
|
videoElement.value = document.createElement('video');
|
videoElement.value.muted = true;
|
videoElement.value.src = URL.createObjectURL(file);
|
await new Promise((resolve, reject) => {
|
videoElement.value.onloadedmetadata = () => {
|
videoElement.value.play();
|
resolve();
|
};
|
videoElement.value.onerror = () => {
|
reject(new Error('视频加载失败'));
|
};
|
});
|
canvas.value = document.createElement('canvas');
|
canvas.value.width = 640;
|
canvas.value.height = 480;
|
ctx.value = canvas.value.getContext('2d');
|
segments.value = [];
|
processFrame();
|
};
|
|
const processFrame = async () => {
|
if (videoElement.value.currentTime < videoElement.value.duration) {
|
ctx.value.clearRect(0, 0, canvas.value.width, canvas.value.height);
|
ctx.value.drawImage(videoElement.value, 0, 0, canvas.value.width, canvas.value.height);
|
|
const imageData = ctx.value.getImageData(0, 0, canvas.value.width, canvas.value.height);
|
const data = imageData.data;
|
for (let i = 0; i < data.length; i += 4) {
|
const r = data[i];
|
const g = data[i + 1];
|
const b = data[i + 2];
|
if (g > r + 30 && g > b + 30) {
|
data[i + 3] = 0;
|
}
|
}
|
ctx.value.putImageData(imageData, 0, 0);
|
|
const segment = canvas.value.toDataURL('image/png');
|
segments.value.push(segment);
|
await new Promise((resolve) => setTimeout(resolve, frameDuration.value));
|
processFrame();
|
} else {
|
processSegments(segments.value, frameRate.value);
|
}
|
};
|
const processSegments = async (segments, frameRate) => {
|
const blobUrls = [];
|
for (let i = 0; i < segments.length; i++) {
|
const data = segments[i].split(',')[1];
|
const byteCharacters = atob(data);
|
const byteNumbers = new Array(byteCharacters.length);
|
for (let j = 0; j < byteCharacters.length; j++) {
|
byteNumbers[j] = byteCharacters.charCodeAt(j);
|
}
|
const byteArray = new Uint8Array(byteNumbers);
|
const blob = new Blob([byteArray], { type: 'image/png' });
|
const blobUrl = URL.createObjectURL(blob);
|
blobUrls.push(blobUrl);
|
await ffmpeg.value.FS('writeFile', `frame${i.toString().padStart(3, '0')}.png`, await fetchFile(blobUrl));
|
}
|
const originalVideoBlob = await fetch(videoElement.value.src).then(response => response.blob());
|
await ffmpeg.value.FS('writeFile', 'original_video.mp4', await fetchFile(originalVideoBlob));
|
await ffmpeg.value.run('-i', 'original_video.mp4', '-vn', '-acodec', 'copy', 'audio.aac');
|
await ffmpeg.value.run('-framerate', frameRate.toString(), '-i', 'frame%03d.png', '-i', 'audio.aac', '-c:v', 'prores_ks', '-pix_fmt', 'yuva444p10le', '-profile:v', '4444', '-c:a', 'copy', 'output.mov');
|
const data = ffmpeg.value.FS('readFile', 'output.mov');
|
const processedBlob = new Blob([data.buffer], { type: 'video/quicktime' });
|
transToFile( processedBlob, fileName.value, fileType.value ).then( res => {
|
emit( 'Thnd', res )
|
} )
|
|
};
|
|
|
const transToFile = async(blob, fileName, fileType) => {
|
return new window.File([blob], fileName, {type: fileType})
|
}
|
|
|
|
</script>
|
|
<style>
|
|
input[type="file"] {
|
|
/* 基本样式 */
|
border: none;
|
background-color: #007bff;
|
border-radius: 5px;
|
cursor: pointer;
|
width: 76px;
|
/* 隐藏文件名 */
|
color: transparent;
|
/* 支持所有浏览器设置字体颜色透明 */
|
&::file-selector-button { color: white } /* 标准语法 */
|
&::-webkit-file-upload-button { color: white } /* Chrome/Safari */
|
&::-moz-file-upload-button { color: white } /* Firefox */
|
}
|
|
/* 统一按钮样式 */
|
input[type="file"]::file-selector-button {
|
background: transparent;
|
border: none;
|
padding: 8px 12px;
|
margin: -8px -12px; /* 抵消容器的padding */
|
font: inherit;
|
}
|
|
/* 浏览器兼容性处理 */
|
input[type="file"]::-webkit-file-upload-button {
|
background: transparent;
|
border: none;
|
padding: 8px 12px;
|
margin: -8px -12px;
|
font: inherit;
|
}
|
|
input[type="file"]::-moz-file-upload-button {
|
background: transparent;
|
border: none;
|
padding: 8px 12px;
|
margin: -8px -12px;
|
font: inherit;
|
}
|
|
input{
|
padding: 8px 12px;
|
}
|
|
</style>
|