du
2025-03-28 04507be7e77642598f7fe9c8e85970df13d9a23c
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
<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>