本工具简单介绍
-
先强调下,本工具可以直接上手使用。
-
本来ffmpeg就可以支持很多功能,比如视频截图,比如可以执行每隔多少秒截取一帧图片,但是本人没找到按时长平均分割截取固定数量的图片的功能,而且一次只能截取一个视频,不能截取指定的整个目录的所有视频,不够自动化,所以就有了这个工具。
-
而本工具可以根据视频的长度来平均截取固定数量的图片,支持传递“源视频文件目录路径”、“输出目录路径”、“共截取图片个数”、“分割方式”等参数,后面有详细说明
-
声明一下,本工具目前只在windows下可用,需要用在其他平台的童鞋可以自行改造。
使用方式
-
首先需要先安装jdk1.8或以上版本,以及ffmpeg这个玩意,不会安装的自行百度哈,官网地址如下:
-
要使用本工具,安装好后需要添加环境变量 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;
}
}