FFmpeg的简单使用【Windows】--- 简单的视频混合拼接

实现功能

点击【选择文件】按钮在弹出的对话框中选择多个视频,这些视频就是一会将要混剪的视频素材,点击【开始处理】按钮之后就会开始对视频进行处理,处理完毕之后会将处理后的文件路径返回,并在页面展示处理后的视频。

视频所作处理:从上传的视频素材中里边的每个视频里边随机抽取 2s,然后将抽取出来的这几个 2s 的视频,随机排序然后进行拼接。

效果展示

需要混剪的素材
处理后的视频

代码实现

说明:

前端代码是使用vue编写的。

后端接口的代码是使用nodejs进行编写的。

前端代码

<template><div id="app"><!-- 显示上传的视频 --><div><h2>将要处理的视频</h2><videov-for="video in uploadedVideos":key="video.src":src="video.src"controlsstyle="width: 100px"></video></div><!-- 上传视频按钮 --><input type="file" @change="uploadVideo" multiple accept="video/*" /><hr /><hr /><!-- 显示处理后的视频 --><div><h2>已处理后的视频</h2><videov-for="video in processedVideos":key="video.src":src="video.src"controlsstyle="width: 100px"></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;console.log(e);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); // 使用实际的文件对象formData.append("videos", video.file); // 使用实际的文件对象}try {const response = await axios.post("http://localhost:3000/user/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"

 multiple:表明这个文件输入框允许多个文件的选择。如果没有这个属性,用户每次只能选择一个文件。

后端代码

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('/upload', upload.single('video'), (req, res) => {const videoPath = req.file.path;const originalName = req.file.originalname;const filePath = path.join('uploads', originalName);fs.rename(videoPath, filePath, (err) => {if (err) {console.error(err);return res.status(500).send("Failed to move file.");}res.json({ message: 'File uploaded successfully.', path: filePath });});
});// 处理多个视频文件上传
router.post('/process', upload.array('videos', 10), (req, res) => {const videoPaths = req.files.map(file => path.join(path.dirname(__filename).replace('routes', 'public/uploads'), file.filename));const outputPath = path.join('public/processed', 'merged_video.mp4');const concatFilePath = path.resolve('public', 'concat.txt').replace(/\\/g, '/');//绝对路径// 创建 processed 目录(如果不存在)if (!fs.existsSync("public/processed")) {fs.mkdirSync("public/processed");}// 计算每个视频的长度const videoLengths = videoPaths.map(videoPath => {return new Promise((resolve, reject) => {ffmpeg.ffprobe(videoPath, (err, metadata) => {if (err) {reject(err);} else {resolve(parseFloat(metadata.format.duration));}});});});// 等待所有视频长度计算完成Promise.all(videoLengths).then(lengths => {// 构建 concat.txt 文件内容let concatFileContent = '';// 定义一个函数来随机选择视频片段function getRandomSegment(videoPath, length) {const segmentLength = 2; // 每个片段的长度为2秒const startTime = Math.floor(Math.random() * (length - segmentLength));return {videoPath,startTime,endTime: startTime + segmentLength};}// 随机选择视频片段const segments = [];for (let i = 0; i < lengths.length; i++) {const videoPath = videoPaths[i];const length = lengths[i];const segment = getRandomSegment(videoPath, length);segments.push(segment);}// 打乱视频片段的顺序function shuffleArray(array) {for (let i = array.length - 1; i > 0; i--) {const j = Math.floor(Math.random() * (i + 1));[array[i], array[j]] = [array[j], array[i]];}return array;}shuffleArray(segments);// 构建 concat.txt 文件内容segments.forEach(segment => {concatFileContent += `file '${segment.videoPath.replace(/\\/g, '/')}'\n`;concatFileContent += `inpoint ${segment.startTime}\n`;concatFileContent += `outpoint ${segment.endTime}\n`;});fs.writeFileSync(concatFilePath, concatFileContent, 'utf8');// 使用 ffmpeg 合并多个视频ffmpeg().input(concatFilePath).inputOptions(['-f concat','-safe 0']).output(outputPath).outputOptions(['-y', // 覆盖已存在的输出文件'-c:v libx264', // 视频编码器'-preset veryfast', // 编码速度'-crf 23', // 视频质量控制'-map 0:v', // 选择所有输入文件的视频流'-an' // 禁用音频]).on('end', () => {const processedVideoSrc = `/processed/merged_video.mp4`;console.log(`Processed video saved at: ${outputPath}`);res.json({ message: 'Videos processed and merged successfully.', path: processedVideoSrc });}).on('error', (err) => {console.error(`Error processing videos: ${err}`);console.error('FFmpeg stderr:', err.stderr);res.status(500).json({ error: 'An error occurred while processing the videos.' });}).run();}).catch(err => {console.error(`Error calculating video lengths: ${err}`);res.status(500).json({ error: 'An error occurred while processing the videos.' });});// 写入 concat.txt 文件const concatFileContent = videoPaths.map(p => `file '${p.replace(/\\/g, '/')}'`).join('\n');fs.writeFileSync(concatFilePath, concatFileContent, 'utf8');
});module.exports = router;

注意:

关于multer配置项 和 ffmpeg() 的说明可移步进行查看FFmpeg的简单使用【Windows】--- 视频倒叙播放-CSDN博客

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 developmentres.locals.message = err.message;res.locals.error = req.app.get('env') === 'development' ? err : {};// render the error pageres.status(err.status || 500);res.render('error');
});module.exports = app;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/446370.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL-08.DDL-表结构操作-创建-案例

一.MySQL创建表的方式 1.首先根据需求文档定义出原型字段&#xff0c;即从需求文档中可以直接设计出来的字段 2.再在原型字段的基础上加上一些基础字段&#xff0c;构成整个表结构的设计 我们采用基于图形化界面的方式来创建表结构 二.案例 原型字段 各字段设计如下&…

JAVA就业笔记4——第二阶段(1)

课程须知 A类知识&#xff1a;工作和面试常用&#xff0c;代码必须要手敲&#xff0c;需要掌握。 B类知识&#xff1a;面试会问道&#xff0c;工作不常用&#xff0c;代码不需要手敲&#xff0c;理解能正确表达即可。 C类知识&#xff1a;工作和面试不常用&#xff0c;代码不…

Redis:分布式 - 主从复制

Redis&#xff1a;分布式 - 主从复制 概念配置主从模式info replicationslave-read-onlytcp-nodelay 命令slaveof 主从结构一主一从一主多从 主从复制流程数据同步命令全量同步部分同步实时同步 节点晋升 概念 Redis的最佳应用&#xff0c;还是要在分布式系统中。对于非分布式…

Dockerfile 详解

Dockerfile是自定义Docker镜像的一套规则&#xff0c;由多条指令构成&#xff0c;每条指令都会对应于Docker镜像中的每一层&#xff0c;因为Docker是分层存储的。以下是Dockerfile中各个参数的详解及演示解析&#xff1a; 1. FROM 功能&#xff1a;指定待扩展的父级镜像&#…

【Linux系列】写入文本到文件

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

智慧乡村可视化设计,让美丽的乡村更加魅力。

智慧乡村可视化设计为美丽的乡村注入了新的活力&#xff0c;使其更加魅力四射。 通过可视化设计&#xff0c;乡村的自然风光得以更生动地展现。高清的全景图像、实时的视频监控&#xff0c;让人们仿佛身临其境&#xff0c;感受乡村的青山绿水、田园风光。 古老的村落、宁静的…

关于int*的*号归属权问题

再根据函数指针定义&#xff1a;int (*int) (int a)。我们发现*和后面的标识符才是一体的 所以int *a,b;的写法更好&#xff0c;说明a是指针类型&#xff0c;b是int类型

Python_函数式编程(生成器、迭代器、动态性)

简单说&#xff1a;时间换空间&#xff01;想要得到庞大的数据&#xff0c;又想让它占用空间少&#xff0c;那就用生成器&#xff01;延迟计算&#xff01;需要的时候&#xff0c;再计算出数据&#xff01; 创建生成器的方式二(生成器函数)生成器函数&#xff1a; 如果一个函数…

算法修炼之路之位运算

目录 一:位运算符及一些常用结论总结 1.给一个数n&#xff0c;确定它的二进制表示中的第x位是0还是1(位数从右向左0开始增加) 2.将一个数n的二进制表示形式的第x位修改成1 3.将一个数n的二进制表示的第x位修改为0 4.提取一个数n的二进制表示中最右侧的1 5.干掉一个数n的…

『Mysql进阶』Mysql explain详解(五)

目录 Explain 介绍 Explain分析示例 explain中的列 1. id 列 2. select_type 列 3. table 列 4. partitions 列 5. type 列 6. possible_keys 列 7. key 列 8. key_len 列 9. ref 列 10. rows 列 11. filtered 列 12. Extra 列 Explain 介绍 EXPLAIN 语句提供有…

24/10/12算法笔记 VGG

VGG特点&#xff1a; 1.深度&#xff1a;非常深 2.卷积核采用3*3&#xff0c;使得网络能够捕捉到更细粒度的图像特征 3.全连接层&#xff1a;使用全连接层来分类 4.使用ReLU激活函数&#xff0c;有助于缓解梯度消失 5.在卷积层和池化层后&#xff0c;使用局部归一化&#…

7-I2C与AHT20温湿度传感器

I2C与AHT20温湿度传感器 嵌入式领域另一种常见的通信IIC通信&#xff0c;并用其与AHT20传感器进行交互&#xff0c;获取房间的温度与湿度。 I2C有一条用于传递数据的数据线称为SDA&#xff08;Serial Data&#xff09;&#xff0c;另一条是用于提供同步时钟脉冲的时钟线SCL&am…

oracle数据坏块处理(一)-通过rman备份修复

表有坏块时&#xff0c;全表查询会报错&#xff1a; 这时候如果有前面正常的rman备份&#xff0c;那么我们就可以通过rman备份直接对数据文件块做恢复 先对数据文件做个逻辑检查&#xff1a; RMAN> backup check logical VALIDATE DATAFILE EXB_DATA/exb/datafile/cuteinf…

使用 three.js和 shader 实现一个五星红旗 飘扬得着色器

使用 three.js和 shader 实现一个五星红旗 飘扬得着色器 源链接&#xff1a;https://threehub.cn/#/codeMirror?navigationThreeJS&classifyshader&idchinaFlag 国内站点预览&#xff1a;http://threehub.cn github地址: https://github.com/z2586300277/three-ce…

【算法思想·二叉树】用「遍历」思维解题 II

本文参考labuladongsuanfa笔记[【强化练习】用「遍历」思维解题 II | labuladong 的算法笔记] 如果让你在二叉树中的某些节点上做文章&#xff0c;一般来说也可以直接用遍历的思维模式。 270. 最接近的二叉搜索树值 | 力扣 | LeetCode | 给你二叉搜索树的根节点 root 和一个目…

Android Studio开发Kotlin项目中遇到的问题解决集

背景&#xff1a;Android Studio 2022.3.1 1.Unexpected tokens (use ; to separate expressions on the same line) 无法在同一行声明一个变量并实例化。 解决&#xff1a;分开 &#xff08;1&#xff09; var aaCo:Runoob<String>aaCoRunoob("aa") &…

阿里云dataworks测试

文章目录 开始查看全局信息查看数据源信息(endpoint与project的信息)查看绑定、解绑钉钉创建、查看AccessKey(Access Key ID与Access Key Secret) 线上开发新建开发节点mysqlpython 本地开发python 程序调度 开始 参考文档&#xff1a;https://help.aliyun.com/zh/ram/user-gu…

Stm32+Esp8266连接阿里云程序移植教程(MQTT协议)

Stm32Esp8266连接阿里云程序移植教程&#xff08;MQTT协议&#xff09; 一、前期准备二、移植过程三、程序的使用3.1 连接上阿里云3.2 传输用户数据到阿里云3.3 解析从阿里云下发给用户的数据3.4 关于调试接口 一、前期准备 自己要的工程文件移植所需的文件&#xff08;如下图&…

UML(统一建模语言)

面向对象设计主要就是使用UML的类图&#xff0c;类图用于描述系统中所包含的类以及它们之间的相互关系&#xff0c;帮助人们简化对系统的理解&#xff0c;它是系统分析和设计阶段的重要产物&#xff0c;也是系统编码和测试的重要模型依据。 画图软件&#xff1a;ProcessOn思维…

ROS2 “通信方式” 参数服务器

为什么加“通信方式”引号&#xff0c;因为我觉得他就不算通信&#xff0c;最多最多就是一个动态加载参数方式 所以ros通信方式就三种&#xff0c;topic service action 别犟&#xff0c;犟就是你对&#xff01; 常用的 param参数方法如下&#xff1a; # declare_parameter…