因为公司项目的部分需求,需要将已经录制好的视频,从固定时间开始截取,到固定时间结束.并且将视频截取成相对平均的若干段视频.目前demo实现了,后续还会继续优化,在视频截取的时候,从关键帧开始或结束.
1.首先需要安装FFmpeg.
2.直接上代码了
//分割视频的大小
private long blockSize = 1 * 1024 * 1024;
@Test
public void Test1() throws Exception {
List lists = cutVideo("/Users/wangge/Desktop/erge.mp4");
System.out.println(lists);
}
视频切割规则计算,切割命令组装
/**
* @param filePath 要处理的文件路径
* @return 分割后的文件路径
* @throws Exception 文件
*/
List cutVideo(String filePath) throws Exception {
File file = new File(filePath);
if (!file.exists()) {
throw new FileNotFoundException(filePath + "文件不存在");
}
if (!filePath.endsWith(".mp4")) {
throw new Exception("文件格式错误");
}
//从ffmpeg获得的时间长度00:00:00格式
String videoTimeString = getVideoTime(file);
log.info("从ffmpeg获得的时间长度00:00:00格式:{}",videoTimeString);
//将时长转换为秒数
int videoSecond = parseTimeToSecond(videoTimeString);
log.info("将时长转换为秒数:{}",videoSecond);
//视频文件的大小
long fileLength = getVideoFileLength(file);
log.info("视频文件的大小:{}",fileLength);
List cutedVideoPaths = new ArrayList();
if (fileLength <= blockSize) {
log.info("如果视频文件大小不大于预设值,则直接返回原视频文件");
cutedVideoPaths.add(filePath);
} else {
log.info("超过预设大小,需要切割");
int partNum = (int) (fileLength / blockSize);
log.info("文件大小除以分块大小的商:{}",partNum);
long remainSize = fileLength % blockSize;
log.info("余数:{}",remainSize);
int cutNum;
if (remainSize > 0) {
cutNum = partNum + 1;
} else {
cutNum = partNum;
}
log.info("cutNum:{}",cutNum);
int eachPartTime = videoSecond / cutNum;
log.info("eachPartTime:{}",eachPartTime);
String fileFolder = file.getParentFile().getAbsolutePath();
log.info("fileFolder:{}",fileFolder);
String fileName[] = file.getName().split("\\.");
log.info("fileName[]:{}",fileName);
for (int i = 0; i < cutNum; i++) {
List commands = Lists.newArrayList();
commands.add("ffmpeg");
commands.add("-ss");
commands.add(parseTimeToString(eachPartTime * i));
if (i != cutNum - 1) {
commands.add("-t");
commands.add(parseTimeToString(eachPartTime));
}
commands.add("-i");
commands.add(filePath);
commands.add("-codec");
commands.add("copy");
commands.add(fileFolder + File.separator + fileName[0] + "_part" + i + "." + fileName[1]);
cutedVideoPaths.add(fileFolder + File.separator + fileName[0] + "_part" + i + "." + fileName[1]);
newRunCommand(commands);
}
}
return cutedVideoPaths;
}
开始逐条执行命令(本身要做成命令批量执行的,但是命令组装出来执行的结果不符合预期,于是就精简了一点,直接循环逐条执行)
private Result newRunCommand(List command) {
log.info("相关命令 command:{}",command);
Result result = new Result(false, "");
ProcessBuilder builder = new ProcessBuilder(command);
builder.redirectErrorStream(true);
try {
Process process = builder.start();
final StringBuilder stringBuilder = new StringBuilder();
final InputStream inputStream = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
while ((line = reader.readLine()) != null){
}
if (reader != null) {
reader.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
throw new RuntimeException("ffmpeg执行异常" + e.getMessage());
}
return result;
}
工具类相关:
/**
* 获取视频文件时长
*
* @param file 文件
* @return 时长 格式hh:MM:ss
* @throws FileNotFoundException 视频不存在抛出此异常
*/
private String getVideoTime(File file) throws FileNotFoundException {
if (!file.exists()) {
throw new FileNotFoundException(file.getAbsolutePath() + "不存在");
}
List commands = new ArrayList();
commands.add("ffmpeg");
commands.add("-i");
commands.add(file.getAbsolutePath());
Result result = runCommand(commands);
String msg = result.getMsg();
if (result.isSuccess()) {
Pattern pattern = Pattern.compile("\\d{2}:\\d{2}:\\d{2}");
Matcher matcher = pattern.matcher(msg);
String time = "";
while (matcher.find()) {
time = matcher.group();
}
return time;
} else {
return "";
}
}
/**
* 获取文件大小
*
* @param file 去的文件长度,单位为字节b
* @return 文件长度的字节数
* @throws FileNotFoundException 文件未找到异常
*/
private long getVideoFileLength(File file) throws FileNotFoundException {
if (!file.exists()) {
throw new FileNotFoundException(file.getAbsolutePath() + "不存在");
}
return file.length();
}
/**
* 将字符串时间格式转换为整型,以秒为单位
*
* @param timeString 字符串时间时长
* @return 时间所对应的秒数
*/
private int parseTimeToSecond(String timeString) {
Pattern pattern = Pattern.compile("\\d{2}:\\d{2}:\\d{2}");
Matcher matcher = pattern.matcher(timeString);
if (!matcher.matches()) {
try {
throw new Exception("时间格式不正确");
} catch (Exception e) {
e.printStackTrace();
}
}
String[] time = timeString.split(":");
return Integer.parseInt(time[0]) * 3600 + Integer.parseInt(time[1]) * 60 + Integer.parseInt(time[2]);
}
/**
* 将秒表示时长转为00:00:00格式
*
* @param second 秒数时长
* @return 字符串格式时长
*/
private String parseTimeToString(int second) {
int end = second % 60;
int mid = second / 60;
if (mid < 60) {
return mid + ":" + end;
} else if (mid == 60) {
return "1:00:" + end;
} else {
int first = mid / 60;
mid = mid % 60;
return first + ":" + mid + ":" + end;
}
}
public class Result {
private boolean success;
private String msg;
public Result(boolean success, String msg) {
this.success = success;
this.msg = msg;
}
public Result() {
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}