java结合ffmpeg实现多个视频截图,按时长平均分割截取固定数量

java结合ffmpeg实现多个视频截图,按时长平均分割截取固定数量

本工具简单介绍
  • 先强调下,本工具可以直接上手使用

  • 本来ffmpeg就可以支持很多功能,比如视频截图,比如可以执行每隔多少秒截取一帧图片,但是本人没找到按时长平均分割截取固定数量的图片的功能,而且一次只能截取一个视频,不能截取指定的整个目录的所有视频,不够自动化,所以就有了这个工具。

  • 而本工具可以根据视频的长度来平均截取固定数量的图片,支持传递“源视频文件目录路径”、“输出目录路径”、“共截取图片个数”、“分割方式”等参数,后面有详细说明

  • 声明一下,本工具目前只在windows下可用,需要用在其他平台的童鞋可以自行改造。

使用方式
  • 首先需要先安装jdk1.8或以上版本,以及ffmpeg这个玩意,不会安装的自行百度哈,官网地址如下:

    http://www.ffmpeg.org/download.html.

  • 要使用本工具,安装好后需要添加环境变量 FFMPEG_HOME,就是ffmpeg的bin目录的绝对路径,目录带不带\都可以,并重启电脑(不重启电脑需要带一个“ffmpeg.exe所在目录”的参数)。

  • 将本文最后的源码保存为Vedios2Images.java文件,然后在cmd命令中执行以下命令,将其编译成Vedios2Images.class

javac -encoding UTF-8 Vedios2Images.java
  • 然后就可以使用Vedios2Images.class对多个视频进行截图了

  • 命令参数含义


命令格式 java VedioToImages [源视频文件路径] [目标目录路径] [截取图片个数] [ffmpeg.exe所在目录] [是否分目录存放截图]

以下参数中,参数1和参数2是必填参数。

参数1:源视频文件目录路径,此目录带不带\都可以,须填绝对路径。必传参数。

参数2:输出目录路径,此目录带不带\都可以,须填绝对路径。必传参数。

参数3:共截取图片个数。 可不传,不传默认为6。

参数4:ffmpeg.exe所在目录。可不传,不填自动从环境变量 FFMPEG_HOME 获取。
		注意:刚配置不重启电脑时java中获取不到,所以不想重启可暂时传入,此目录带不带\都可以,须填绝对路径。
		若不想重启又不想传此参数,可以将其安装(重命名后解压)到D:\tools_install\ffmpeg\bin 目录
		下(ffmpeg.exe在这个目录就行)。

参数5:表示是否将每个视频的截图分割为目录,不填不分割。值为s则将每个视频切分到以视频名命
名的目录中,否则直接放在输出目录中。始终为最后一个参数。


如 :
	java Vedios2Images D:\tmp\test D:\jietu 20 D:\tools_install\ffmpeg\bin s

	表示将D:\tmp\test目录下的所有视频截图,输出到D:\jietu目录下,每个视频的输出个数为20个,
	D:\tools_install\ffmpeg\bin 显式的指定了ffmpeg.exe所在目录,最后的s代表将每个视频的多个
	截图存放在以视频名命名的目录中。
	
	下面的命令也是正确的:
		java Vedios2Images D:\tmp\test D:\jietu
		
		java Vedios2Images D:\tmp\test D:\jietu  10
		
		java Vedios2Images D:\tmp\test D:\jietu s
	
		java Vedios2Images D:\tmp\test D:\jietu  10 s
	
效果展示
  • 命令指定out为输出目录,source为源目录
    在这里插入图片描述

  • source中有两个视频
    在这里插入图片描述

  • 截图前out目录为空(不为空也可以)
    在这里插入图片描述

  • 在Vedios2Images.class所在目录下打开cmd窗口,并输入示例命令:

    java Vedios2Images D:\tmp\test\source D:\tmp\test\out 15 s
    

在这里插入图片描述

  • 执行完成
    在这里插入图片描述
  • out目录中已有截图,且按目录分割

在这里插入图片描述

  • 视频01
    在这里插入图片描述
  • 视频02
    在这里插入图片描述
  • 清空out目录,执行下面的不带 s 参数的命令
java Vedios2Images D:\tmp\test\source D:\tmp\test\out 15

在这里插入图片描述

  • 执行完毕后
    在这里插入图片描述
  • 截图不按目录分割
    在这里插入图片描述
工具源码

时间仓促,代码可能有比较多不足的地方,望见谅!

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Vedios2Images {

    public static void main(String[] args) throws Exception {
        //去前后空格并校验参数个数不能小于2
        trimAndCheckArgs(args);
        File[] vedios = new File(args[0]).listFiles();
        //目录中不能有其他文件
        for(File vedio : vedios){
            if(vedio.isDirectory() || Objects.equals(vedio.getName(), "Vedios2Images.class")){
                continue;
            }
            String vedioPath = vedio.getAbsolutePath();
            String outPath;

            if(isSplit(args)){
                outPath = getOutSplitToDirPath(args, vedio.getName());
            }else{
                outPath = getOutPath(args, vedio.getName());
            }
            int imgCount = getImgCount(args);
            String ffmpegPath = getFfmpegPath(args);

            singleVedioToImages(vedioPath, outPath, imgCount, ffmpegPath);

        }

        System.out.println("执行完毕!!!");
    }

    private static boolean isSplit(String[] args) {
        //大于两个参数,且最后一个参数值为s说明为分目录截图
        return args.length > 2 && Objects.equals("s", args[args.length - 1]);
    }

    private static void trimAndCheckArgs(String [] args) {
        if(args.length < 2){
            throw new RuntimeException("至少需要传入两个参数:源视频目录 和 截图输出目录");
        }
        for (int i = 0; i < args.length; i++){
            args[i] = args[i].trim();
        }
    }

    private static String getOutPath(String[] args, String vedioName){

        String outPath = "D:\\jietu\\img_%d.jpg";
        if(args.length > 1){
            if(!args[1].endsWith("\\")){
                args[1] += "\\";
            }
            String vedioNameIgnoreSufix = vedioName.substring(0, vedioName.lastIndexOf("."));
            outPath = args[1] + vedioNameIgnoreSufix + "_%d.jpg";
        }
        return outPath;
    }

    /**
     * 获取视频截图路径,每个视频截图放在以各自文件名命名的目录
     * @param args args
     * @param vedioName vedioName
     * @return 截图路径
     */
    private static String getOutSplitToDirPath(String[] args, String vedioName){

        String outPath = "D:\\jietu\\img_%d.jpg";
        if(args.length > 1){
            if(!args[1].endsWith("\\")){
                args[1] += "\\";
            }
            String vedioNameIgnoreSufix = vedioName.substring(0, vedioName.lastIndexOf("."));
            String destDir = args[1] + vedioNameIgnoreSufix + "\\";
            if(!new File(args[1]).exists()){
                throw new RuntimeException("目录 " + args[1] + "不存在");
            }
            new File(destDir).mkdir();
            outPath = destDir + vedioNameIgnoreSufix + "_%d.jpg";
        }
        return outPath;
    }

    private static int getImgCount(String[] args){
        //默认截取6张
        int imgCount = 6;
        if(args.length > 2 && isInteger(args[2])){
            imgCount = Integer.parseInt(args[2]);
        }
        return imgCount;
    }

    private static String getFfmpegPath(String[] args){
        String ffmpegHome = System.getenv("FFMPEG_HOME");
        if(ffmpegHome == null){
            if(args.length == 3 && !isInteger(args[2])){
                pathAdapter(args,2);
                ffmpegHome = args[2];
            }else if(args.length > 3){
                pathAdapter(args,3);
                ffmpegHome = args[3];
            }else{
                ffmpegHome = "D:\\tools_install\\ffmpeg\\bin\\";
            }
        }
        //FFMPEG_HOME
        return ffmpegHome + "ffmpeg";
    }


    public static boolean isInteger(String str) {
        Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$");
        return pattern.matcher(str).matches();
    }

    private static void singleVedioToImages(String vedioPath, String outPath,
                                            int imgCount, String ffmpegPath) throws Exception {

        List<String> commands = new ArrayList<>();
        //ffmpeg -i 001.mp4 -vf fps=0.1 -q:v 2 -f image2 D:\jietu\test-%d.jpg
        commands.add(ffmpegPath);
        commands.add("-i");
        commands.add(vedioPath);
        Long vedioTime = getVedioTime(commands);
        commands.add("-vf");
        double fpsParam = 1.0 / ((double) vedioTime / imgCount);
        commands.add("fps=" + fpsParam);
        commands.add("-q:v");
        commands.add("2");
        commands.add("-f");
        commands.add("image2");
        commands.add(outPath);

        ProcessBuilder builder = new ProcessBuilder();
        builder.command(commands);
        Process process = builder.start();
//        process.waitFor();
        //从输入流中读取视频信息
        BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()));
//        StringBuilder stringBuilder = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) {
//            stringBuilder.append(line);
            System.out.println(line);
        }
        br.close();
        System.out.println("视频 " + vedioPath + " 截图完毕");
    }

    private static void pathAdapter(String[] args, int index){
        if(!args[index].endsWith("\\")){
            args[index] += "\\";
        }
    }

    /**
     * 获取视频总时间
     */
    private static Long getVedioTime(List<String> commands/*, String vedioPath, String ffmpegPath*/) throws Exception{
        /*List<String> commands = new ArrayList<>();
        commands.add(ffmpegPath);
        commands.add("-i");
        commands.add(vedioPath);*/

        ProcessBuilder builder = new ProcessBuilder();
        builder.command(commands);
        Process p = builder.start();

        //从输入流中读取视频信息
        BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
        StringBuilder stringBuilder = new StringBuilder();
        String line = "";
        while ((line = br.readLine()) != null) {
            stringBuilder.append(line);
        }
        br.close();

        //从视频信息中解析时长
        String regexDuration = "Duration: (.*?), start: (.*?), bitrate: (\\d*) kb\\/s";
        Pattern pattern = Pattern.compile(regexDuration);
        Matcher m = pattern.matcher(stringBuilder.toString());
        if (m.find()) {
            Long time = getTimelen(m.group(1));
//            System.out.println("视频时长:" + time + "s , 开始时间:" + m.group(2) + ", 比特率:" + m.group(3) + "kb/s");
            return time;
        }
        return null;
    }


    private static Long getTimelen(String timelen) {
        Long min = 0L;
        String strs[] = timelen.split(":");
        if (strs[0].compareTo("0") > 0) {
            // 秒
            min += Long.parseLong(strs[0]) * 60 * 60;
        }
        if (strs[1].compareTo("0") > 0) {
            min += Long.parseLong(strs[1]) * 60;
        }
        if (strs[2].compareTo("0") > 0) {
            min += Math.round(Double.parseDouble(strs[2]));
        }
        return min;
    }


}


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值