ffmpeg使用及java操作

1.文档

官网: FFmpeg
官方使用文档: ffmpeg Documentation

中文简介: https://www.cnblogs.com/leisure_chn/p/10297002.html

函数及时间: ffmpeg日记1011-过滤器-语法高阶,逻辑,函数使用_ffmpeg gte(t,2)-CSDN博客

java集成ffmpeg: SpringBoot集成ffmpeg实现视频转码播放_jave-all-deps-CSDN博客 

2.命令

复制视频片段,并调整视频质量,crf一般为23,crf越大压缩越厉害,质量下降越多
ffmpeg -i test.mp4 -ss 00:30 -to 00:50 -c:v libx264 -crf 23 newTest1.mp4

调整视频分辨率(调整的分辨率超过原来的,不能提升画质)
ffmpeg -i test.mp4 -vf scale=432:240 video_240p.mp4 -hide_banner
ffmpeg -i test.mp4 -vf scale=-1:240 video_240p.mp4 -hide_banner

加水印(10:10水印距离左上角的像素距离)
ffmpeg -i test.mp4 -i used100.png -filter_complex "overlay=10:10" testOutputWater.mp4

--保真加水印,不怎么改变视频编码及质量
ffmpeg -i test.mp4 -i used100.png -filter_complex "overlay=10:10" -c:v libx264 -c:a copy testOutputWater2.mp4

--视频2秒后开始显示水印,gte(t\,2)
ffmpeg -i test.mp4 -vf "movie=used100.png[logo];[in][logo]overlay=x='if(gte(t\,2)\,0\,NAN)'" testOutputWater6.mp4

--在指定时间范围(20-30秒)显示水印,between(t\,20\,30)
ffmpeg -i test.mp4 -vf "movie=used100.png[logo];[in][logo]overlay=x='if(between(t\,20\,30)\,0\,NAN)'" testOutputWater9.mp4

加硬字幕
ffmpeg -i test.mp4 -vf subtitles=test.srt mp4_add_captions1.mp4

3.java代码

        maven依赖,使用集成的ffmpeg程序,缺点是打成的jar包很大,优点是不需要手动安装ffmpeg

        <!--   音视频   -->
        <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,手动在程序运行的服务器上安,优点是打成的jar包较小,关于如何手动在服务器上配置ffmpeg,请见附录2

        <!--音视频操作-->
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-core</artifactId>
            <version>3.0.1</version>
        </dependency>

        工具类

package mis.shared.file;

import lombok.extern.slf4j.Slf4j;
import mis.shared.date.DateUnitl;
import org.springframework.util.StringUtils;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.info.MultimediaInfo;
import ws.schild.jave.info.VideoSize;
import ws.schild.jave.process.ProcessWrapper;
import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.Calendar;
import java.util.Objects;

/**
 * ffmpeg工具类
 *
 * @since 2024/1/22
 */
@Slf4j
public class FfmpegUtil {

    public static void main(String[] args) throws Exception {
        VideoSize videoSize = new VideoSize(480, 800);
        System.out.println(GetCompressSize(videoSize, 720, 480));
    }

    /**
     * 通过本地路径获取多媒体文件信息(宽,高,时长,编码等)
     *
     * @param filePath 文件路径
     * @return MultimediaInfo 媒体对象,包含 (宽,高,时长,编码等)
     */
    public static MultimediaInfo GetMediaInfo(String filePath) {
        try {
            return new MultimediaObject(new File(filePath)).getInfo();
        } catch (EncoderException e) {
            log.error("获取媒体信息异常!", e);
        }
        return null;
    }

    /**
     * 修改视频分辨率
     * 修改分辨率超过原视频,不能提高画质
     * 标清SD(Standard Definition) 480p 640x480 720x480
     * 高清 HD(High Definition) 720p 960x720 1280x720
     * 1080p 1440x1080 1920x1080
     * 超高清UHD(Ultra High Definition) 4k 4096×3112 4096*2160
     *
     * @param inputPath  视频来源地址
     * @param outputPath 输出视频地址
     * @param width      宽度
     * @param height     高度
     */
    public static boolean ChangeScale(String inputPath, String outputPath, int width, int height) {
        ProcessWrapper ffmpeg = null;
        try {
            if (new File(outputPath).exists()) {
                log.error("目标文件已存在,outputPath:{}", outputPath);
                return false;
            }

            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-vf");
            ffmpeg.addArgument("scale=w=" + width + ":h=" + height);
            ffmpeg.addArgument("-c:a");
            ffmpeg.addArgument("copy");
            ffmpeg.addArgument(outputPath);
            ffmpeg.execute();

            //等待完成
            String errorMsg = WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("修改视频分辨率失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("修改视频分辨率异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }

    /**
     * 调整视频质量
     *
     * @param inputPath    输入视频
     * @param outputPath   输出视频
     * @param qualityLevel 质量等级,0-51,0最高
     */
    public static boolean ChangeQuality(String inputPath, String outputPath, int qualityLevel) {
        ProcessWrapper ffmpeg = null;
        try {
            if (new File(outputPath).exists()) {
                log.error("目标文件已存在,outputPath:{}", outputPath);
                return false;
            }

            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-q:v");
            ffmpeg.addArgument(qualityLevel + "");
            ffmpeg.addArgument("-c:a");
            ffmpeg.addArgument("copy");
            ffmpeg.addArgument(outputPath);
            ffmpeg.execute();

            //等待完成
            String errorMsg = WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("修改视频视频质量失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("调整视频质量异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }

    /**
     * 复制视频片段
     *
     * @param inputPath  视频来源地址
     * @param outputPath 输出视频地址
     * @param startTime  开始时间,01:02:03在视频的第1小时第2分钟第3秒处
     * @param endTime    结束时间,格式同startTime
     */
    public static boolean CopyVideo(String inputPath, String outputPath, String startTime, String endTime) {
        ProcessWrapper ffmpeg = null;
        try {
            if (new File(outputPath).exists()) {
                log.error("目标文件已存在,outputPath:{}", outputPath);
                return false;
            }
            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-ss");
            ffmpeg.addArgument(startTime);
            ffmpeg.addArgument("-to");
            ffmpeg.addArgument(endTime);
            ffmpeg.addArgument("-c:v");
            ffmpeg.addArgument("copy");
            ffmpeg.addArgument("-c:a");
            ffmpeg.addArgument("copy");
            ffmpeg.addArgument(outputPath);
            //执行
            ffmpeg.execute();

            //等待完成
            String errorMsg = WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("复制视频失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("复制视频片段异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }

    /**
     * 格式转换
     *
     * @param inputPath  视频来源地址
     * @param outputPath 输出视频地址
     */
    public static boolean TransferFormat(String inputPath, String outputPath) {
        ProcessWrapper ffmpeg = null;
        try {
            if (new File(outputPath).exists()) {
                log.error("目标文件已存在,outputPath:{}", outputPath);
                return false;
            }
            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-c");
            ffmpeg.addArgument("copy");
            ffmpeg.addArgument(outputPath);
            //执行
            ffmpeg.execute();

            //等待完成
            String errorMsg = WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("转换视频格式失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("转换视频格式异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }

    /**
     * 生成视频第多少秒的缩略图
     */
    public static boolean GenerateThumbnail(String inputPath, int second, String outputPath) {
        ProcessWrapper ffmpeg = null;
        try {
            File target = new File(outputPath);
            if (target.exists()) {
                log.error("目标文件已存在,outputPath:{}", outputPath);
                return false;
            }

            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-ss");//第多少秒的视频画面
            ffmpeg.addArgument("" + second);
            ffmpeg.addArgument("-frames:v");//第多少秒的视频画面
            ffmpeg.addArgument("1");
            ffmpeg.addArgument(outputPath);
            ffmpeg.execute();

            //等待完成
            String errorMsg = WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("获取缩略图失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("获取视频缩略图异常", e);
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
        return false;
    }

    /**
     * 添加水印
     *
     * @param inputPath     输入视频路径
     * @param waterMarkPath 水印文件路径
     * @param x             距离左上角水平距离
     * @param y             距离左上角垂直距离
     * @param outputPath    输出视频路径
     */
    public static boolean AddWatermark(String inputPath, String waterMarkPath,
                                       int x, int y, String outputPath) {
        ProcessWrapper ffmpeg = null;
        try {
            File target = new File(outputPath);
            if (target.exists()) {
                log.error("目标文件已存在,outputPath:{}", outputPath);
                return false;
            }

            ffmpeg = new DefaultFFMPEGLocator().createExecutor();
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(inputPath);
            ffmpeg.addArgument("-i");
            ffmpeg.addArgument(waterMarkPath);
            ffmpeg.addArgument("-filter_complex");
            ffmpeg.addArgument("overlay=" + x + ":" + y);
            ffmpeg.addArgument(outputPath);
            ffmpeg.execute();

            //等待完成
            String errorMsg = WaitFfmpegFinish(ffmpeg);
            if (StringUtils.hasLength(errorMsg)) {
                log.error("视频添加水印失败,文件:" + inputPath, errorMsg);
                return false;
            }
            return true;
        } catch (Exception e) {
            log.error("视频添加水印异常", e);
            return false;
        } finally {
            if (ffmpeg != null) {
                ffmpeg.destroy();
            }
        }
    }

    /**
     * 获取ffmpeg默认路径
     */
    public static String GetDefaultFFMPEGPath() {
        return new File(System.getProperty("java.io.tmpdir"), "jave/").getAbsolutePath();
    }

    /**
     * 将第多少秒转换为视频时间(HH:mm:ss)
     * 例: 第70秒为00:01:10
     *
     * @param second 视频中的第多少秒
     */
    public static String TransferVideoTime(int second) {
        var calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.add(Calendar.SECOND, second);
        return DateUnitl.ToString(calendar.getTime(), "HH:mm:ss");
    }

    /**
     * 等待命令执行完成
     *
     * @return 成功返回null, 否则返回错误信息
     */
    private static String WaitFfmpegFinish(ProcessWrapper processWrapper) throws Exception {
        //存储执行错误信息
        var errorSb = new StringBuilder();
        //ffmpeg的执行过程打印在errorStream中
        try (BufferedReader br = new BufferedReader(new InputStreamReader(processWrapper.getErrorStream()))) {
            String processInfo;
            while ((processInfo = br.readLine()) != null) {
                errorSb.append(processInfo).append("\r\n");
            }
            //执行完成后,获取处理退出编码
            int processExitCode = processWrapper.getProcessExitCode();
            if (processExitCode != 0) {//非0执行失败,返回执行过程信息
                return errorSb.toString();
            }
        } catch (Exception e) {
            log.error("WaitFfmpegFinish异常!", e);
            return errorSb.toString();
        }
        return null;
    }

    /**
     * 获取按照指定宽、高比压缩的视频大小
     * 宽度和高度必须是2的倍数
     */
    public static VideoSize GetCompressSize(VideoSize oldVideoSize, int weight, int height) {
        assert oldVideoSize != null;
        int oldWidth = oldVideoSize.getWidth();
        int oldHeight = oldVideoSize.getHeight();
        double ratio = (double) oldWidth / oldHeight;
        if (ratio > 1.0) {//宽屏视频,根据高度缩小
            if (oldHeight <= height) {//原高度比预期小,直接返回
                return oldVideoSize;
            }
            int newWeight = (oldWidth * height) / oldHeight;
            if (newWeight % 2 != 0) {
                newWeight++;
            }
            return new VideoSize(newWeight, height);
        } else {//窄屏视频,根据宽度缩小
            if (oldWidth <= weight) {//原宽带度比预期小,直接返回
                return oldVideoSize;
            }
            int newHeight = (oldHeight * weight) / oldWidth;
            if (newHeight % 2 != 0) {
                newHeight++;
            }
            return new VideoSize(weight, newHeight);
        }
    }

    //判断视频大小是否相等
    public static boolean Equal(VideoSize videoSize1, VideoSize videoSize2) {
        if (videoSize1 == null && videoSize2 == null) {
            return true;
        } else if (videoSize1 == null || videoSize2 == null) {
            return false;
        } else {
            return Objects.equals(videoSize1.getHeight(), videoSize2.getHeight())
                    && Objects.equals(videoSize1.getWidth(), videoSize2.getWidth());
        }
    }

}

附录1

         字幕test.srt

1
00:00:20,000 --> 00:00:30,000
这是视频第20秒到30秒将显示的字幕

2
00:00:50,000 --> 00:00:60,000
这是视频第50秒到60秒将显示的字幕

附录2

        关于ffmpeg在服务器上的位置,参考如下代码

  public DefaultFFMPEGLocator() {
    String os = System.getProperty("os.name").toLowerCase();
    boolean isWindows = os.contains("windows");
    boolean isMac = os.contains("mac");
    LOG.debug("Os name is <{}> isWindows: {} isMac: {}", os, isWindows, isMac);

    // Dir Folder
    File dirFolder = new File(System.getProperty("java.io.tmpdir"), "jave/");
    if (!dirFolder.exists()) {
      LOG.debug(
          "Creating jave temp folder to place executables in <{}>", dirFolder.getAbsolutePath());
      dirFolder.mkdirs();
    } else {
      LOG.debug("Jave temp folder exists in <{}>", dirFolder.getAbsolutePath());
    }

    // -----------------ffmpeg executable export on disk.-----------------------------
    String suffix = isWindows ? ".exe" : (isMac ? "-osx" : "");
    String arch = System.getProperty("os.arch");

    // File
    File ffmpegFile = new File(dirFolder, "ffmpeg-" + arch + "-" + Version.getVersion() + suffix);
    LOG.debug("Executable path: {}", ffmpegFile.getAbsolutePath());

    // Check the version of existing .exe file
    if (ffmpegFile.exists()) {
      // OK, already present
      LOG.debug("Executable exists in <{}>", ffmpegFile.getAbsolutePath());
    } else {
      LOG.debug("Need to copy executable to <{}>", ffmpegFile.getAbsolutePath());
      copyFile("ffmpeg-" + arch + suffix, ffmpegFile);
    }

    // Need a chmod?
    if (!isWindows) {
      try {
        Runtime.getRuntime().exec(new String[] {"/bin/chmod", "755", ffmpegFile.getAbsolutePath()});
      } catch (IOException e) {
        LOG.error("Error setting executable via chmod", e);
      }
    }

    // Everything seems okay
    path = ffmpegFile.getAbsolutePath();
    if (ffmpegFile.exists())
    {
        LOG.debug("ffmpeg executable found: {}", path);
    }
    else
    {
        LOG.error("ffmpeg executable NOT found: {}", path);
    }
  }

       windows环境,打开cmd窗口,执行echo %TEMP%获取临时文件夹,打开临时文件夹,创建子文件夹jave(最终路径例子C:\Users\TN\AppData\Local\Temp\jave)

        下载ws.schild的maven对应系统jar包,并解压jave-nativebin-win64-3.0.1,找到里面的ffmpeg-amd64.exe复制到上面的目录下

         linux通过脚本获取默认目录,原理是通过编译运行FfmpegHelper.java获得

#!/bin/bash

cd /tmp

# download 
if [ -e "/tmp/FfmpegHelper.java" ]
then
    echo "FfmpegHelper.java already exist!"
else
    echo "FfmpegHelper.java does not exist,start download"
    wget https://fs-im-kefu.7moor-fs1.com/29397395/4d2c3f00-7d4c-11e5-af15-41bf63ae4ea0/1705980395189/FfmpegHelper.java
fi

# compile
if [ -e "/tmp/FfmpegHelper.class" ]
then
    echo "FfmpegHelper.class already exist!"
else
    echo "FfmpegHelper.class does not exist,start compile"
    javac FfmpegHelper.java
fi

# run
java FfmpegHelper

# clean
rm -rf /tmp/FfmpegHelper.class
rm -rf /tmp/FfmpegHelper.java

        FfmpegHelper.java

import java.io.File;

public class FfmpegHelper {
	
    public static String GetDefaultFFMPEGPath() {
        return new File(System.getProperty("java.io.tmpdir"), "jave/").getAbsolutePath();
    }

    public static void main(String[] args) {
        String defaultFFMPEGPath = GetDefaultFFMPEGPath();
        System.out.println(defaultFFMPEGPath);
    }

}

  • 12
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java 使用 FFmpeg 时,可以使用 `Process` 类来启动 FFmpeg 进程,并通过 `waitFor()` 方法等待 FFmpeg 进程完成。具体可以使用以下代码: ```java import java.io.IOException; public class Main { public static void main(String[] args) throws IOException, InterruptedException { ProcessBuilder pb = new ProcessBuilder("ffmpeg", "-i", "input.mp4", "output.mp4"); Process process = pb.start(); process.waitFor(); process.destroy(); } } ``` 上述代码,我们使用 `ProcessBuilder` 对象创建一个启动 FFmpeg 进程的命令,并通过 `start()` 方法启动 FFmpeg 进程。然后使用 `waitFor()` 方法等待 FFmpeg 进程完成,并使用 `destroy()` 方法关闭 FFmpeg 进程。 需要注意的是,在使用 FFmpeg 时,可能会出现 FFmpeg 进程无法结束的情况。这时可以通过向 FFmpeg 进程发送 `SIGINT` 信号来手动结束 FFmpeg 进程。具体可以使用以下代码: ```java import java.io.IOException; public class Main { public static void main(String[] args) throws IOException, InterruptedException { ProcessBuilder pb = new ProcessBuilder("ffmpeg", "-i", "input.mp4", "output.mp4"); Process process = pb.start(); process.waitFor(); process.destroy(); process.getInputStream().close(); process.getOutputStream().close(); process.getErrorStream().close(); process.destroyForcibly(); } } ``` 上述代码,我们在关闭 FFmpeg 进程之前,先关闭了 FFmpeg 进程的输入流、输出流和错误流,并使用 `destroyForcibly()` 方法强制关闭 FFmpeg 进程。这样可以确保 FFmpeg 进程被完全关闭。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kenick

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值