莫方教程网

专业程序员编程教程与实战案例分享

【JS Nextjs】nextjs+ffmpeg实现自定义视频压缩

之前一篇文章介绍了如何使用命令行来操作FFmpeg进行音视频的各种处理。如何安装ffmpeg

FFmpeg功能非常强大,可以处理各种格式的音视频,包括格式转换,音视频裁剪,添加水印,调整声道,音视频压缩。FFmpeg的功能丰富,但只使用命令行来操作的话,会不太方便,那么如何才能界面化操作FFpeg呢。

今天给大家分享一下,前端如何界面化使用FFmpeg。

Nextjs + fluent-ffmpeg 实现自定义视频压缩

完整的demo已放在gitee上,有兴趣的朋友可以看下next-demo: next-demo


fluent-ffmpeg

文档地址:「链接」

  1. 在使用 fluent-ffmpeg之前需要手动安装FFmpeg,因为fluent-ffmpeg是将FFmpeg的参数式调用封装成函数的链式调用。如何安装FFmpeg,可以看下之前的文章,windows和mac安装比较相似,都需要配置系统路劲。如何安装ffmpeg
  2. 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

  1. 使用 App Router,路由基于文件系统,创建界面和定义api都非常方便,很适合小团队或者个人来搭建一些业务场景不是很复杂的系统。
  2. 在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 实现自定义视频压缩

前端界面逻辑

  1. 将fluent-ffmpeg支持的参数都展示在页面
  2. 展示原始文件信息和压缩完的文件信息

API接口逻辑

  1. 接收前端入参
  2. 根据入参链式调用ffmpeg
  3. 根据处理状态,返回前端各种状态
  4. 简单介绍一下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,里面有完整的项目代码以及其他场景分享

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言