前端压缩视频(@ffmpeg/ffmpeg)和图片(browser-image-compression)
·
前端压缩视频(@ffmpeg/ffmpeg)和图片(browser-image-compression)
1.压缩图片
安装插件
npm install browser-image-compression --save
引入插件
import { createWorker } from 'browser-image-compression';
定义方法
// 压缩图片
async compressImage(file, maxSizeMB) {
const options = {
maxSizeMB,
maxWidthOrHeight: 1920,
useWebWorker: true
};
try {
const compressedFile = await imageCompression(file, options);
const fileExtension = file.name.split('.').pop();
const compressedFileName = `${Date.now()}.${fileExtension}`;
return new File([compressedFile], compressedFileName, { type: file.type });
} catch (error) {
console.error('图片压缩失败:', error);
return file; // 压缩失败则使用原文件
}
},
2.压缩视频
安装插件
npm install @ffmpeg/ffmpeg@0.12.6 --save
npm install @ffmpeg/util@0.12.1 --save
npm install @ffmpeg/core@0.12.4 --save
引入插件
import { FFmpeg } from '@ffmpeg/ffmpeg'
import { fetchFile ,toBlobURL} from '@ffmpeg/util' // 注意来源包变化
定义方法
// 初始化 FFmpeg
async initFFmpeg() {
this.initStatus = 'loading'
try {
this.ffmpeg = new FFmpeg()
// 加载关键资源
await this.ffmpeg.load({
coreURL: '/public/ffmpeg-core.js',
wasmURL: '/public/ffmpeg-core.wasm',
workerURL: '/public/ffmpeg-core.worker.js'
})
// 验证初始化完成
if (!this.ffmpeg.loaded) {
throw new Error('FFmpeg 核心加载失败')
}
console.log('FFmpeg 初始化完成');
this.initStatus = 'ready'
this.retryCount = 0
} catch (err) {
this.initStatus = 'error'
console.error('初始化失败:', err)
}
}
// 压缩视频
async compressVideo() {
if (!this.ffmpeg?.loaded) {
alert('FFmpeg 未初始化完成,请稍后重试')
return
}
try {
await this.ffmpeg.writeFile('input.mp4', await fetchFile(this.currentFile))
await this.executeCompression(10)
return await this.outputResult()
} catch (err) {
console.log(222);
// this.handleError(err)
}
},
// 开始压缩
async executeCompression(maxSizeMB) {
const bitrate = maxSizeMB * 8 * 1024 * 1024 / 10; // 计算目标比特率
const crf = 23; // 常用的 CRF 值,数值越小质量越高
console.log('executeCompression');
try {
await this.ffmpeg.exec([
'-i', 'input.mp4',
'-b:v', `${6000}k`,
'-vf', 'scale=640:-1', // 调整分辨率,
'-preset', 'fast', // 编码速度
'output.mp4'
]);
console.log('Compression completed successfully');
} catch (err) {
console.error('Compression failed:', err);
// 处理错误,例如显示错误消息
this.status = '压缩失败: ' + err.message;
}
},
// 输出压缩结果
async outputResult() {
console.log('outputResult');
const data = await this.ffmpeg.readFile('output.mp4')
const fileExtension = this.currentFile.name.split('.').pop();
const compressedFileName = `${Date.now()}.${fileExtension}`;
return new File([data], compressedFileName, { type: 'video/mp4' });
// const url = URL.createObjectURL(new Blob([data], { type: 'video/mp4' }))
// 处理下载或预览...
},
完整的代码
<template>
<div class="container">
<!-- <input type="file" @change="handleFileUpload" accept="video/*" /> -->
<input type="file" @change="handleFileUpload" />
<button :disabled="isProcessing" @click="compress">
{{ isProcessing ? '处理中...' : '开始压缩' }}
</button>
<!-- 进度条 -->
<div v-for="(progress, index) in uploadProgresses" :key="index" class="progress-container">
<div class="file-name">{{ progress.fileName }}</div>
<div class="progress-bar">
<div class="progress" :style="{ width: progress.percentage + '%' }"></div>
</div>
<div class="progress-text">{{ progress.percentage }}%</div>
</div>
<!-- 状态提示 -->
<div class="status">{{ status }}</div>
<!-- 压缩结果预览 -->
<video v-if="compressedUrl" :src="compressedUrl" controls class="preview"></video>
<a v-if="compressedUrl" :href="compressedUrl" download="compressed.mp4">下载压缩文件</a>
</div>
</template>
<script>
import imageCompression from 'browser-image-compression';
import { FFmpeg } from '@ffmpeg/ffmpeg'
import axios from 'axios';
import { fetchFile ,toBlobURL} from '@ffmpeg/util' // 注意来源包变化
// const baseURL = 'https://cdn.jsdelivr.net/npm/@ffmpeg/core-mt@0.12.9/dist/esm'
export default {
data() {
return {
isProcessing: false,
progress: 0,
status: '',
compressedUrl: null,
ffmpeg: null,
currentFile: null,
retryCount: 0,
initStatus: 'loading', // 初始化状态:loading, ready, error
uploadProgresses:[]
}
},
methods: {
// 更新上传进度
updateUploadProgress(index, percentage) {
this.uploadProgresses[index].percentage = percentage;
},
// 上传文件
async uploadFiles(files) {
const uploadPromises = files.map((file, index) => {
const formData = new FormData();
formData.append('files', file);
return axios.post('http://localhost:9080/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: (progressEvent) => {
console.log(progressEvent);
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
this.updateUploadProgress(index, percentCompleted);
}
});
});
try {
await Promise.all(uploadPromises);
alert('文件上传成功');
} catch (error) {
console.error('上传失败:', error);
alert('文件上传失败');
}
},
//获取上传文件
async handleFileUpload(e) {
this.currentFile = e.target.files[0]
this.uploadProgresses.push({
fileName: this.currentFile.name,
percentage: 0
});
},
// 压缩图片
async compressImage(file, maxSizeMB) {
const options = {
maxSizeMB,
maxWidthOrHeight: 1920,
useWebWorker: true
};
try {
const compressedFile = await imageCompression(file, options);
const fileExtension = file.name.split('.').pop();
const compressedFileName = `${Date.now()}.${fileExtension}`;
return new File([compressedFile], compressedFileName, { type: file.type });
} catch (error) {
console.error('图片压缩失败:', error);
return file; // 压缩失败则使用原文件
}
},
// 压缩视频
async compressVideo() {
if (!this.ffmpeg?.loaded) {
alert('FFmpeg 未初始化完成,请稍后重试')
return
}
try {
await this.ffmpeg.writeFile('input.mp4', await fetchFile(this.currentFile))
await this.executeCompression(10)
return await this.outputResult()
} catch (err) {
console.log(222);
// this.handleError(err)
}
},
// 开始压缩
async compress() {
if (!this.currentFile) {
alert('请选择一个视频文件')
return
}
this.isProcessing = true
const compressedFiles = [];
let compressedFile;
if(this.currentFile.type.includes('image')){
compressedFile = await this.compressImage(this.currentFile, 1); // 设置最大文件大小为1MB
}else if(this.currentFile.type.includes('video')){
compressedFile = await this.compressVideo()
}
compressedFiles.push(compressedFile);
this.isProcessing = false
this.uploadFiles(compressedFiles);
},
// 开始压缩
async executeCompression(maxSizeMB) {
const bitrate = maxSizeMB * 8 * 1024 * 1024 / 10; // 计算目标比特率
const crf = 23; // 常用的 CRF 值,数值越小质量越高
console.log('executeCompression');
try {
await this.ffmpeg.exec([
'-i', 'input.mp4',
'-b:v', `${6000}k`,
'-vf', 'scale=640:-1', // 调整分辨率,
'-preset', 'fast', // 编码速度
'output.mp4'
]);
console.log('Compression completed successfully');
} catch (err) {
console.error('Compression failed:', err);
// 处理错误,例如显示错误消息
this.status = '压缩失败: ' + err.message;
}
},
// 输出压缩结果
async outputResult() {
console.log('outputResult');
const data = await this.ffmpeg.readFile('output.mp4')
const fileExtension = this.currentFile.name.split('.').pop();
const compressedFileName = `${Date.now()}.${fileExtension}`;
return new File([data], compressedFileName, { type: 'video/mp4' });
// const url = URL.createObjectURL(new Blob([data], { type: 'video/mp4' }))
// 处理下载或预览...
},
async initFFmpeg() {
this.initStatus = 'loading'
try {
this.ffmpeg = new FFmpeg()
// 加载关键资源
await this.ffmpeg.load({
coreURL: '/public/ffmpeg-core.js',
wasmURL: '/public/ffmpeg-core.wasm',
workerURL: '/public/ffmpeg-core.worker.js'
// coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
// wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
// workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, 'text/javascript')
})
// 验证初始化完成
if (!this.ffmpeg.loaded) {
throw new Error('FFmpeg 核心加载失败')
}
console.log('FFmpeg 初始化完成');
this.initStatus = 'ready'
this.retryCount = 0
} catch (err) {
this.initStatus = 'error'
console.error('初始化失败:', err)
}
}
},
async mounted() {
try {
this.initFFmpeg()
} catch (err) {
console.error('FFmpeg 初始化失败:', err)
}
}
}
</script>
<style>
.container {
max-width: 600px;
margin: 2rem auto;
padding: 20px;
}
input[type="file"] {
margin-bottom: 1rem;
}
button {
background: #42b983;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
.progress-container {
margin: 1rem 0;
height: 20px;
border-radius: 10px;
position: relative;
}
.progress-bar {
height: 100%;
background: #42b983;
border-radius: 10px;
transition: width 0.3s ease;
}
.progress-container span {
position: absolute;
right: 10px;
top: 1px;
color: #333;
}
.status {
margin: 1rem 0;
color: #666;
}
.preview {
width: 100%;
margin-top: 1rem;
border: 1px solid #ddd;
}
a {
display: inline-block;
margin-top: 1rem;
color: #42b983;
text-decoration: none;
}
.file-upload-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.progress-container {
margin: 10px 0;
}
.file-name {
margin-bottom: 5px;
font-size: 14px;
}
.progress-bar {
height: 20px;
border: 1px solid #ccc;
border-radius: 5px;
overflow: hidden;
background-color: #f5f5f5;
}
.progress {
height: 100%;
background-color: #4CAF50;
width: 0;
}
.progress-text {
margin-top: 5px;
text-align: center;
}
</style>
本地起node+express服务
const express = require('express');
const multer = require('multer');
const cors = require('cors');
const path = require('path');
const fs = require('fs');
const app = express();
const port = 9080;
// 配置 CORS
app.use(cors());
app.use((req, res, next) => {
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
next();
});
// 配置 multer 存储
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/'); // 上传文件的存储目录
},
filename: function (req, file, cb) {
cb(null, Date.now() + path.extname(file.originalname)); // 重命名文件,避免文件名冲突
}
});
// 配置文件过滤
const fileFilter = (req, file, cb) => {
if (file.mimetype.startsWith('image/') || file.mimetype.startsWith('video/') || file.mimetype === 'application/gzip') {
cb(null, true); // 接受文件
} else {
cb(null, false); // 拒绝文件
cb(new Error('文件类型不被允许'));
}
};
// 配置 multer
const upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 100 // 限制文件大小为 100MB
},
fileFilter: fileFilter
});
// 创建上传目录
if (!fs.existsSync('uploads')) {
fs.mkdirSync('uploads');
}
// 多文件上传接口
app.post('/upload', upload.array('files'), (req, res) => {
if (req.files && req.files.length > 0) {
const files = req.files.map(file => ({
filename: file.filename,
originalname: file.originalname,
mimetype: file.mimetype,
size: file.size
}));
res.status(200).json({
message: '文件上传成功',
data: files
});
} else {
res.status(400).json({ message: '文件上传失败' });
}
});
// 启动服务器
app.listen(port, () => {
console.log(`服务器运行在 http://localhost:${port}`);
});
更多推荐


所有评论(0)