之前一篇文章介绍了如何使用命令行来操作FFmpeg进行音视频的各种处理。如何安装ffmpeg
FFmpeg功能非常强大,可以处理各种格式的音视频,包括格式转换,音视频裁剪,添加水印,调整声道,音视频压缩。FFmpeg的功能丰富,但只使用命令行来操作的话,会不太方便,那么如何才能界面化操作FFpeg呢。
今天给大家分享一下,前端如何界面化使用FFmpeg。
Nextjs + fluent-ffmpeg 实现自定义视频压缩
完整的demo已放在gitee上,有兴趣的朋友可以看下next-demo: next-demo
fluent-ffmpeg
文档地址:「链接」
- 在使用 fluent-ffmpeg之前需要手动安装FFmpeg,因为fluent-ffmpeg是将FFmpeg的参数式调用封装成函数的链式调用。如何安装FFmpeg,可以看下之前的文章,windows和mac安装比较相似,都需要配置系统路劲。如何安装ffmpeg
- fluent-ffmpeg 的链式调用,非常方便业务场景操作
//简单介绍一下链式调用
ffmpeg(inputPath)
.output(outputFilePath)
.videoCodec(videoCode) // 使用 H.264 编码
.size(width+''+height) // 设置分辨率宽度为 320,高度按比例缩放
.videoBitrate(2500+'k') // 设置视频码率为 2500 kbps
.fps(+videoFrameRate) // 设置帧率为 30 fps [24,25,30,60,120]
.setStartTime(start) // 从第 10 秒开始
.setDuration(cutout_duration) // 裁剪 20 秒
.outputOptions('-crf ' + videoQulity) // 设置 视频质量 CRF 为 23 (0-51)
.on('start', (commandLine:any) => {
console.log('FFmpeg process started with command:', commandLine);
})
.on('progress', (progress:any) => {
console.log('Processing: ' , progress);
console.log(`Processing: ${progress.percent}% done`);
})
.on('end', () => {
console.log('Video compression finished');
resolve('Video compression finished');
})
.on('error', (err:Error) => {
console.error('Error compressing video:', err);
reject(err);
})
.run();
Nextjs
- 使用 App Router,路由基于文件系统,创建界面和定义api都非常方便,很适合小团队或者个人来搭建一些业务场景不是很复杂的系统。
- 在app/api 目录下定义文件,直接导出http的请求方法(GET,POST,DELET,PUT),即可定义我们需要的接口api
import { NextRequest, NextResponse } from "next/server";
export function GET(request: NextRequest) {
return NextResponse.json({ message: 'Hello World' },{status:200});
}
export function POST(request: NextRequest) {
return NextResponse.json({ message: 'Hello World' },{status:200});
}
export function PUT(request: NextRequest) {
return NextResponse.json({ message: 'Hello World' },{status:200});
}
export function DELETE(request: NextRequest) {
return NextResponse.json({ message: 'Hello World' },{status:200});
}
Nextjs + fluent-ffmpeg 实现自定义视频压缩
前端界面逻辑
- 将fluent-ffmpeg支持的参数都展示在页面
- 展示原始文件信息和压缩完的文件信息
API接口逻辑
- 接收前端入参
- 根据入参链式调用ffmpeg
- 根据处理状态,返回前端各种状态
- 简单介绍一下api的代码
import { NextResponse, NextRequest } from 'next/server';
import ffmpeg from 'fluent-ffmpeg';
import fs from 'fs';
import path from 'path';
import { formatSeconds } from '@/utils'
import formatDate from '@/utils/day'
// 设置 FFmpeg 路径 window
//在window系统下必须设置
ffmpeg.setFfmpegPath('C:\\ffmpeg\\ffmpeg\\bin\\ffmpeg.exe');
// 设置 FFmpeg 路径 mac
// ffmpeg.setFfmpegPath('/Users/ffmpeg/ffmpeg');
export async function POST(request:NextRequest) {
try {
const formData = await request.formData();
const file = formData.get('file') as File;
if (!file) {
return NextResponse.json({ message: 'No file uploaded' }, { status: 400 });
}
// 创建 uploads 文件夹
const uploadsDir = path.join(process.cwd(), 'public/video');
if (!fs.existsSync(uploadsDir)) {
fs.mkdirSync(uploadsDir,{ recursive: true });
}
// 将文件保存到临时目录
const fileName = file.name;
const tempFilePath = path.join(uploadsDir, fileName);
console.log('tempFilePath===>',tempFilePath)
const fileBuffer = await file.arrayBuffer();
fs.writeFileSync(tempFilePath, Buffer.from(fileBuffer));
// 生成输出文件名
const outputFileName = `${formatDate(new Date()).format('YYYYMMDDhhmmss')}.${fileName}`;
// 生成输出文件路径
const outputFilePath = path.join(uploadsDir, `${outputFileName}`);
// 初始化 FFmpeg 处理流程
let ffmpegProcess = ffmpeg(tempFilePath);
// 根据前端传递的参数动态配置 FFmpeg 命令
ffmpegProcess = ffmpegProcess.output(outputFilePath)
// 视频编码器
const videoCodec = formData.get('videoCode') as string;
if (videoCodec) {
ffmpegProcess = ffmpegProcess.videoCodec(videoCodec);
}
// 视频码率
const videoBitrate = formData.get('videoBitrate') as string;
if (videoBitrate) {
ffmpegProcess = ffmpegProcess.videoBitrate(videoBitrate+'k');
}
//省略一些业务代码
...省略业务代码,主要是业务参数
// 获取压缩视频的信息
const stats = fs.statSync(outputFilePath);
const fileSizeInBytes = stats.size;
const fileFormat = path.extname(outputFileName).substring(1);
const response = {
url: `/video/${outputFileName}`,
name:outputFileName,
size: fileSizeInBytes,
format: fileFormat,
}
return NextResponse.json({ data:response,code:200,message:'Video compression successful' }, { status: 200 });
}catch(err) {
return NextResponse.json({ message: 'Video compression failed', error: error }, { status: 500 });
}
}
前端代码
// 界面的参数传入
const handleSubmit = async (e: any) => {
e.preventDefault();
if (!file) return;
setLoading(true);
const formData = new FormData();
formData.append('file', file);
for (const key in settingInfo) {
const value = settingInfo[key as keyof ISetting]
formData.append(key, '' + value);
}
try {
const response = await fetch('/api/shot', {
method: 'POST',
body: formData,
});
if (response.status === 200) {
const data = await response.json();
console.log('data', data);
setCompressionInfo(data.data);
} else {
console.log('ERROR: Failed to compress video');
}
} catch (error) {
console.error('=====>Error:', error);
} finally {
setLoading(false);
}
};
由于篇幅问题,文章只展示核心代码逻辑,有兴趣的朋友,可以去gitee上看下next-demo: next-demo,里面有完整的项目代码以及其他场景分享