• 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();
        }
    
  • 相关阅读:
    减少.NET应用程序内存占用的一则实践
    ASP.NET中检测图片真实否防范病毒上传
    PHP、Python 相关正则函数实例
    利用脚本将java回归到面向函数式编程
    ASP.Net 实现伪静态方法及意义
    ExecuteNonQuery()方法
    在.net中使用split方法!
    str.split()如何用?谢谢
    输出货币型
    (DataRowView)e.Item.DataItem只有在ItemDataBound这个事件中起效
  • 原文地址:https://www.cnblogs.com/bananafish/p/16379241.html
Copyright © 2020-2023  润新知