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();
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章