实现功能
点击【选择文件】按钮可以选择视频,当点击【开始处理】按钮之后,会先将视频上传到服务器,然后开始进行视频倒叙播放的处理,当视频处理完毕之后会将输出的文件路径返回,同时在页面中将处理好的视频展示出来。
效果展示
代码实现
说明:
前端代码是使用vue编写的。
后端接口的代码是使用nodejs进行编写的。
前端代码
<template>
<div id="app">
<!-- 显示上传的视频 -->
<div>
<h2>将要处理的视频</h2>
<video
v-for="video in uploadedVideos"
:key="video.src"
:src="video.src"
controls
style="width: 200px"
></video>
</div>
<!-- 上传视频按钮 -->
<input type="file" @change="uploadVideo" accept="video/*" />
<!-- 显示处理后的视频 -->
<div>
<h2>已处理后的视频</h2>
<video
v-for="video in processedVideos"
:key="video.src"
:src="video.src"
controls
style="width: 200px"
></video>
</div>
<button @click="processVideos">开始处理</button>
</div>
</template>
<script setup>
import axios from "axios";
import { ref } from "vue";
const uploadedVideos = ref([]);
const processedVideos = ref([]);
let videoIndex = 0;
const uploadVideo = async (e) => {
const files = e.target.files;
for (let i = 0; i < files.length; i++) {
const file = files[i];
const videoSrc = URL.createObjectURL(file);
uploadedVideos.value.push({ id: videoIndex++, src: videoSrc, file });
}
};
const processVideos = async () => {
const formData = new FormData();
for (const video of uploadedVideos.value) {
formData.append("video", video.file); // 使用实际的文件对象
}
try {
const response = await axios.post(
"http://localhost:3000/user/single/process",
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
}
);
const processedVideoSrc = response.data.path;
processedVideos.value.push({
id: videoIndex++,
src: "http://localhost:3000/" + processedVideoSrc,
});
} catch (error) {
console.error("Error processing video:", error);
}
};
</script>
补充说明:
accept="video/*":指定了只接受视频文件类型,这将过滤掉非视频文件,使得用户在选择文件时只能看到并选择视频文件。
video/*:是一个通配符,表示所有已知的视频文件类型。如果你只想接受特定的视频格式(例如MP4和WebM),你可以指定他们,如下所示:
accept=".mp4, .webm"
或者,如果你想要更精确地控制,可以使用MIME类型:
accept="video/mp4, video/webm"
后端代码
routers =》users.js
var express = require('express');
var router = express.Router();
const multer = require('multer');
const ffmpeg = require('fluent-ffmpeg');
const path = require('path');
const upload = multer({
dest: 'public/uploads/',
storage: multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'public/uploads'); // 文件保存的目录
},
filename: function (req, file, cb) {
// 提取原始文件的扩展名
const ext = path.extname(file.originalname).toLowerCase(); // 获取文件扩展名,并转换为小写
// 生成唯一文件名,并加上扩展名
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const fileName = uniqueSuffix + ext; // 新文件名
cb(null, fileName); // 文件名
}
})
});
const fs = require('fs');
// 处理单个视频文件
router.post('/single/process', upload.single('video'), (req, res) => {
console.log(req.file)
const videoPath = req.file.path;
// 使用filename进行拼接是为了防止视频被覆盖
const outputPath = `public/processed/reversed_${req.file.filename}`;
ffmpeg()
.input(videoPath)
.outputOptions([
'-vf reverse'// 反转视频帧顺序
])
.output(outputPath)
.on('end', () => {
res.json({ message: 'Video processed successfully.', path: outputPath.replace('public', '') });
})
.on('error', (err) => {
console.log(err)
res.status(500).json({ error: 'An error occurred while processing the video.' });
})
.run();
});
module.exports = router;
multer配置项说明:
- 配置选项:
- dest:指定了文件的临时存储路径,如果提供了storage选择,则dest可能不会被使用。
- storage:使用multer.diskStorage() 来定义文件如何存储在磁盘上。
- destination函数:
- 这个函数用于指定文件存储的目录
- req:请求对象
- file:包含了关于上传文件的信息
- cb:回调函数,用于通知multer存储路径
- filename函数:
- 这个函数用于生成存储在磁盘上的文件名
- req:请求对象
- file:含有文件的相关信息
- cb:回调函数,用于通知穆拉特人文件名
- 在这里,我们生成了一个唯一的文件名,该文件名包含当前时间戳和一个随机数,以避免文件名冲突,并保留了原始文件的扩展名。
ffmpeg()说明:
当你调用
ffmpeg()
时,实际上创建了一个ffmpeg
实例。这个实例可以用来设置输入文件、输出文件以及一系列的处理选项。
ffmpeg()
返回的是一个Command
对象,这个对象包含了用于设置和执行ffmpeg
命令的方法。这些方法可以链接起来形成一个流式操作链。
- .input():指定输入文件路径
- .output():指定输出文件路径
- .on()、.once():监听事件,比如完成、错误等
- .run():执行ffmpeg命令
- .cancel():取消正在运行的命令
- .getCommand():获取将要执行的完整命令字符串
典型的ffmpeg命令执行流程
- 初始化:创建 ffmpeg 示例并指定输入文件
- 设置输出文件:使用 .output() 方法来设置文件路径
- 添加命令行参数:使用 .addOption() 或者链式调用 .option() 方法来添加额外的 ffmpeg参数
- 监听事件:监听 end 或 error 事件来处理命令执行的结果
- 执行命令:调用 .run() 方法来开始执行命令
routers =》index.js
var express = require('express');
var router = express.Router();
router.use('/user', require('./users'));
module.exports = router;
app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// 使用cors解决跨域问题
app.use(require('cors')());
app.use('/', indexRouter);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;