Fork me on GitHub

spring boot 集成 ffmpeg

spring boot 集成 ffmpeg

什么是 ffmpeg

FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。

ffmpeg 使用C++ 开发,所有功能集成在一个exe程序里面,对我们调用者来说就是一串命令,所以集成方式有很多种,最底层就是使用java去执行拼接好的命令。

下载ffmpeg

下载地址 https://github.com/BtbN/FFmpeg-Builds/releases 下载对应系统版本。

解压出来后有三个exe文件,ffmpeg(音视频编辑工具),ffplay(音视频解码播放工具),ffprobe(流媒体分析工具),这里我们只使用ffmpeg

将 ffmpeg 加入系统环境变量,方便全局调用。

方法一

添加额外命令行拼接jar

  • 添加maven
    <dependency>
      <groupId>ws.schild</groupId>
      <artifactId>jave-all-deps</artifactId>
      <version>3.0.1</version>
      <exclusions>
        <!--  排除windows 32位系统      -->
        <exclusion>
          <groupId>ws.schild</groupId>
          <artifactId>jave-nativebin-win32</artifactId>
        </exclusion>
        <!--  排除linux 32位系统      -->
        <exclusion>
          <groupId>ws.schild</groupId>
          <artifactId>jave-nativebin-linux32</artifactId>
        </exclusion>
        <!-- 排除Mac系统-->
        <exclusion>
          <groupId>ws.schild</groupId>
          <artifactId>jave-nativebin-osx64</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  • 利用ffmpeg.addArgumen 添加ffmpeg参数
package com.ffm.test.util;

// 各种excepting 可自定义,或者直接改成 Exception
import com.mobvoi.ffm.common.exception.BaseException;
import com.mobvoi.ffm.common.exception.CombineException;
import com.mobvoi.ffm.common.exception.FrameRateException;
import com.mobvoi.ffm.common.exception.PicException;
import com.mobvoi.ffm.common.model.dto.SourceDetailInfoDto;
import com.mobvoi.ffm.common.resp.ResultCode;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import lombok.extern.slf4j.Slf4j;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.info.MultimediaInfo;
import ws.schild.jave.process.ProcessWrapper;
import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator;

/**
 * 核心工具包
 *
 * @author shuishan
 * @since 2021/7/22
 */
@Slf4j
public class FfmpegUtil {

  /**
   * 获取音视频时长
   *
   * @param sourcePath
   * @return
   * @throws EncoderException
   */
  public static long getFileDuration(String sourcePath) throws EncoderException {
    MultimediaObject multimediaObject = new MultimediaObject(new File(sourcePath));
    MultimediaInfo multimediaInfo = multimediaObject.getInfo();
    return multimediaInfo.getDuration();
  }


  /**
   * 剪切音视频
   *
   * @param sourcePath
   * @param targetPath
   * @param offetTime   起始时间,格式 00:00:00.000   小时:分:秒.毫秒
   * @param endTime   同上
   * @throws Exception
   */
  public static void cutAv(String sourcePath, String targetPath, String offetTime, String endTime) {
    try {
      ProcessWrapper ffmpeg = new DefaultFFMPEGLocator().createExecutor();
      ffmpeg.addArgument("-ss");
      ffmpeg.addArgument(offetTime);
      ffmpeg.addArgument("-t");
      ffmpeg.addArgument(endTime);
      ffmpeg.addArgument("-i");
      ffmpeg.addArgument(sourcePath);
      ffmpeg.addArgument("-vcodec");
      ffmpeg.addArgument("copy");
      ffmpeg.addArgument("-acodec");
      ffmpeg.addArgument("copy");
      ffmpeg.addArgument(targetPath);
      ffmpeg.execute();
      try (BufferedReader br = new BufferedReader(new InputStreamReader(ffmpeg.getErrorStream()))) {
        blockFfmpeg(br);
      }
      log.info("切除视频成功={}", targetPath);
    } catch (IOException e) {
      throw new CombineException(ResultCode.ERROR.getCode(), "剪切视频失败", null);
    }
  }

  /**
   * 等待命令执行成功,退出
   *
   * @param br
   * @throws IOException
   */
  private static void blockFfmpeg(BufferedReader br) throws IOException {
    String line;
    // 该方法阻塞线程,直至合成成功
    while ((line = br.readLine()) != null) {
      doNothing(line);
    }
  }

  /**
   * 打印日志,调试阶段可解开注释,观察执行情况
   *
   * @param line
   */
  private static void doNothing(String line) {
//    log.info(line);
  }


  /**
   * 合并两个视频
   *
   * @param firstFragmentPath   资源本地路径或者url
   * @param secondFragmentPath  资源本地路径或者url**
   * @param targetPath     目标存储位置
   * @throws Exception
   */
  public static void mergeAv(String firstFragmentPath, String secondFragmentPath,
      String targetPath) {
    try {
      log.info("合并视频处理中firstFragmentPath={},secondFragmentPath={},请稍后.....", firstFragmentPath,
          secondFragmentPath);
      ProcessWrapper ffmpeg = new DefaultFFMPEGLocator().createExecutor();
      ffmpeg.addArgument("-i");
      ffmpeg.addArgument(firstFragmentPath);
      ffmpeg.addArgument("-i");
      ffmpeg.addArgument(secondFragmentPath);
      ffmpeg.addArgument("-filter_complex");
      ffmpeg.addArgument(
          "\"[0:v] [0:a] [1:v] [1:a] concat=n=2:v=1:a=1 [v] [a]\" -map \"[v]\" -map \"[a]\"");
      ffmpeg.addArgument(targetPath);
      ffmpeg.execute();
      try (BufferedReader br = new BufferedReader(new InputStreamReader(ffmpeg.getErrorStream()))) {
        blockFfmpeg(br);
      }
      log.info("合并视频成功={}", targetPath);
    } catch (IOException e) {
      throw new CombineException(ResultCode.ERROR.getCode(), "合并视频失败", null);
    }
  }


  /**
   * 获取视频原声
   *
   * @param sourcePath  本地路径或者url
   * @param targetPath  本地存储路径
   */
  public static String getAudio(String sourcePath, String targetPath, String taskId) {
    try {
      ProcessWrapper ffmpeg = new DefaultFFMPEGLocator().createExecutor();
      ffmpeg.addArgument("-i");
      ffmpeg.addArgument(sourcePath);
      ffmpeg.addArgument("-f");
      ffmpeg.addArgument("mp3");
      ffmpeg.addArgument("-vn");
      ffmpeg.addArgument(targetPath);
      ffmpeg.execute();
      try (BufferedReader br = new BufferedReader(new InputStreamReader(ffmpeg.getErrorStream()))) {
        blockFfmpeg(br);
      }
      log.info("获取视频音频={}", targetPath);
    } catch (IOException e) {
      throw new CombineException(ResultCode.ERROR.getCode(), "获取视频音频失败", taskId);
    }
    return targetPath;
  }

  /**
   * 合并音频
   *
   * @param originAudioPath  音频url或本地路径
   * @param magicAudioPath  音频url或本地路径
   * @param audioTargetPath  目标存储本地路径
   */
  public static void megerAudioAudio(String originAudioPath, String magicAudioPath,
      String audioTargetPath, String taskId) {
    try {
      ProcessWrapper ffmpeg = new DefaultFFMPEGLocator().createExecutor();
      ffmpeg.addArgument("-i");
      ffmpeg.addArgument(originAudioPath);
      ffmpeg.addArgument("-i");
      ffmpeg.addArgument(magicAudioPath);
      ffmpeg.addArgument("-filter_complex");
      ffmpeg.addArgument("amix=inputs=2:duration=first:dropout_transition=2");
      ffmpeg.addArgument("-f");
      ffmpeg.addArgument("mp3");
      ffmpeg.addArgument("-y");
      ffmpeg.addArgument(audioTargetPath);
      ffmpeg.execute();
      try (BufferedReader br = new BufferedReader(new InputStreamReader(ffmpeg.getErrorStream()))) {
        blockFfmpeg(br);
      }
      log.info("合并音频={}", audioTargetPath);
    } catch (IOException e) {
      throw new CombineException(ResultCode.ERROR.getCode(), "合并音频失败", taskId);
    }
  }



  /**
   * 视频加声音
   *
   * @param videoPath   视频
   * @param megerAudioPath  音频
   * @param videoTargetPath 目标地址
   * @param taskId  可忽略,自行删除taskid
   * @throws Exception
   */
  public static void mergeVideoAndAudio(String videoPath, String megerAudioPath,
      String videoTargetPath, String taskId) {
    try {
      ProcessWrapper ffmpeg = new DefaultFFMPEGLocator().createExecutor();
      ffmpeg.addArgument("-i");
      ffmpeg.addArgument(videoPath);
      ffmpeg.addArgument("-i");
      ffmpeg.addArgument(megerAudioPath);
      ffmpeg.addArgument("-codec");
      ffmpeg.addArgument("copy");
      ffmpeg.addArgument("-shortest");
      ffmpeg.addArgument(videoTargetPath);
      ffmpeg.execute();
      try (BufferedReader br = new BufferedReader(new InputStreamReader(ffmpeg.getErrorStream()))) {
        blockFfmpeg(br);
      }
      log.info("获取视频(去除音频)={}", videoTargetPath);
    } catch (IOException e) {
      throw new CombineException(ResultCode.ERROR.getCode(), "获取视频(去除音频)失败", taskId);
    }
  }

  /**
   * 视频增加字幕
   *
   * @param videoPath
   * @param sutitleVideoSavePath
   * @param wordPath  固定格式的srt文件地址或存储位置,百度即可
   * @return
   * @throws Exception
   */
  public static boolean addSubtitle(String videoPath, String sutitleVideoSavePath,
      String wordPath, String taskId) {
    try {
      log.info("开始合成字幕....");
      ProcessWrapper ffmpeg = new DefaultFFMPEGLocator().createExecutor();
      ffmpeg.addArgument("-i");
      ffmpeg.addArgument(videoPath);
      ffmpeg.addArgument("-i");
      ffmpeg.addArgument(wordPath);
      ffmpeg.addArgument("-c");
      ffmpeg.addArgument("copy");
      ffmpeg.addArgument(sutitleVideoSavePath);
      ffmpeg.execute();
      try (BufferedReader br = new BufferedReader(new InputStreamReader(ffmpeg.getErrorStream()))) {
        blockFfmpeg(br);
      }
      log.info("添加字幕成功={}", videoPath);
    } catch (IOException e) {
      throw new CombineException(ResultCode.ERROR.getCode(), "添加字幕失败", taskId);
    }
    return true;
  }

  /**
   * 图片生成视频   帧率设置25,可自行修改
   *
   * @param videoPath
   * @param videoPath
   * @return
   * @throws Exception
   */
  public static boolean picToVideo(String picsPath, String videoPath, String taskId) {
    try {
      log.info("图片转视频中....");
      ProcessWrapper ffmpeg = new DefaultFFMPEGLocator().createExecutor();
      ffmpeg.addArgument("-i");
      ffmpeg.addArgument(picsPath);
      ffmpeg.addArgument("-r");
      ffmpeg.addArgument("25");
      ffmpeg.addArgument("-y");
      ffmpeg.addArgument(videoPath);
      ffmpeg.execute();
      try (BufferedReader br = new BufferedReader(new InputStreamReader(ffmpeg.getErrorStream()))) {
        blockFfmpeg(br);
      }
      log.info("图片转视频成功={}", videoPath);
    } catch (IOException e) {
      log.error("图片转视频失败={}", e.getMessage());
      throw new PicException(ResultCode.ERROR.getCode(), "图片转视频失败", taskId);
    }
    return true;
  }


  /**
   * 获取视频信息
   *
   * @param url
   * @return
   */
  public static MultimediaInfo getVideoInfo(URL url) {
    try {
      MultimediaObject multimediaObject = new MultimediaObject(url);
      return multimediaObject.getInfo();
    } catch (EncoderException e) {
      log.error("获取视频信息报错={}", e.getMessage());
      throw new BaseException(ResultCode.ERROR.getCode(), "获取视频信息报错");
    }
  }

方法二

拼接底层命令执行

  public static String runCmd(String command) {
        StringBuilder sb =new StringBuilder();
        try {
            Process process=Runtime.getRuntime().exec("ffmpeg -i \"input.mp4\" -c:v copy -c:a copy -y -hide_banner \"output.mp4\"");
            InputStream inputStream = process.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader=new BufferedReader(inputStreamReader);
            String line;
            while((line=bufferedReader.readLine())!=null)
            {
                sb.append(line+"\n");
            }
        } catch (Exception e) {
            return null;
        }
        return sb.toString();
    }
posted @ 2022-06-15 17:31  Banana_Fish  阅读(4089)  评论(0编辑  收藏  举报