package org.sxkj.odm.utils;
|
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
|
import java.io.*;
|
import java.nio.file.DirectoryStream;
|
import java.nio.file.Files;
|
import java.nio.file.Path;
|
import java.nio.file.Paths;
|
|
/**
|
* 视频转换图片工具类
|
* @author zhongrj
|
*/
|
public class VideoConverterUtils {
|
|
private static final Logger logger = LoggerFactory.getLogger(VideoConverterUtils.class);
|
|
/**
|
* 视频切图
|
* @param inputPath 视频路径
|
* @param outputPattern 图片输出路径
|
* @param interval 每多少帧取一个图片保存
|
* @throws IOException
|
* @throws InterruptedException
|
* @return
|
*/
|
public static boolean extractFrames(String inputPath, String outputPattern, int interval) throws IOException, InterruptedException {
|
boolean flag = false;
|
// 构建FFmpeg命令
|
String[] ffmpegCommand = {
|
"ffmpeg",
|
"-i", inputPath, // 输入文件
|
"-vf", "select=not(mod(n\\," + interval + "))", // 选择每interval帧中的一帧
|
"-vsync", "vfr", // 可变帧率处理
|
"-q:v", "2", // 输出图片质量(2-31,2为最高质量)
|
"-frame_pts", "1", // 使用帧号作为输出文件名的一部分
|
outputPattern // 输出文件模式
|
};
|
|
try {
|
// 执行FFmpeg命令
|
Process process = new ProcessBuilder(ffmpegCommand).start();
|
|
// 读取错误流(FFmpeg的输出通常写在错误流)
|
BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
|
String line;
|
while ((line = errorReader.readLine()) != null) {
|
System.out.println(line);
|
}
|
|
// 等待命令执行完成
|
int exitCode = process.waitFor();
|
if (exitCode == 0) {
|
flag = true;
|
logger.info("视频切图完成!");
|
} else {
|
flag = false;
|
logger.info("视频切图失败,退出码: " + exitCode);
|
}
|
} catch (IOException | InterruptedException e) {
|
logger.error("视频切图失败,异常消息:{}",e.getMessage());
|
e.printStackTrace();
|
throw e;
|
}
|
return flag;
|
}
|
|
/**
|
* 视频切图-限制cpu,内存使用
|
* @param inputPath 视频路径
|
* @param outputPattern 图片输出路径
|
* @param interval 每多少帧取一个图片保存
|
* @throws IOException
|
* @throws InterruptedException
|
* @return
|
*/
|
public static boolean extractFramesRestriction(String inputPath, String outputPattern, int interval) throws IOException, InterruptedException {
|
// 防止接口重复写入(重试时)
|
if(hasJpegFileNIO(inputPath)){
|
return false;
|
}
|
boolean flag = false;
|
// 构建FFmpeg命令
|
String[] ffmpegCommand = {
|
"ffmpeg",
|
"-loglevel", "error", // 减少日志输出量
|
"-threads", "1", // 限制线程数
|
"-hwaccel", "auto", // 启用硬件加速
|
"-i", inputPath, // 输入文件
|
"-max_muxing_queue_size", "100", // 限制复用队列大小
|
"-vf", "select=not(mod(n\\," + interval + "))", // 选择每interval帧中的一帧
|
"-vsync", "0", // 禁用帧同步缓冲
|
"-q:v", "3", // 输出图片质量(2-31,2为最高质量)
|
"-frame_pts", "1", // 使用帧号作为输出文件名的一部分
|
"-f", "image2", // 明确指定图像格式
|
outputPattern // 输出文件模式
|
};
|
|
try {
|
ProcessBuilder pb = new ProcessBuilder(ffmpegCommand);
|
|
// 重定向错误流到临时文件,避免内存堆积
|
File errorLog = File.createTempFile("ffmpeg_error", ".log");
|
pb.redirectError(errorLog);
|
|
Process process = pb.start();
|
|
// 不缓冲输出流,立即释放内存
|
try (InputStream ignored = process.getInputStream();
|
InputStream ignored2 = process.getErrorStream()) {
|
int exitCode = process.waitFor();
|
|
if (exitCode == 0) {
|
flag = true;
|
logger.info("视频切图完成!");
|
} else {
|
flag = false;
|
logger.error("视频切图失败,退出码: {}。错误日志: {}",
|
exitCode, errorLog.getAbsolutePath());
|
}
|
}
|
errorLog.delete(); // 清理临时文件
|
} catch (IOException | InterruptedException e) {
|
logger.error("视频切图失败,异常消息:{}", e.getMessage());
|
throw e;
|
}
|
return flag;
|
}
|
|
/**
|
* 判断路径下是否已经包含了 jpeg 文件
|
* @param inputPath
|
* @return
|
*/
|
public static boolean hasJpegFileNIO(String inputPath) {
|
try {
|
Path dir = Paths.get(inputPath);
|
|
if (!Files.isDirectory(dir)) {
|
return false;
|
}
|
|
// 使用DirectoryStream逐项处理,不一次性加载所有文件
|
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
|
for (Path path : stream) {
|
if (Files.isRegularFile(path)) {
|
String fileName = path.getFileName().toString().toLowerCase();
|
if (fileName.endsWith(".jpeg") || fileName.endsWith(".jpg")) {
|
return true;
|
}
|
}
|
}
|
}
|
} catch (Exception e) {
|
e.printStackTrace();
|
}
|
return false;
|
}
|
|
/**
|
* 从视频中提取字幕为SRT文件
|
* @param inputPath 视频路径
|
* @param srtPath 输出SRT文件路径
|
* @return true表示成功,false表示失败
|
* @throws InterruptedException 进程被中断
|
* @throws IOException 文件操作失败
|
* @return
|
*/
|
public static boolean extractSrt(String inputPath, String srtPath)
|
throws InterruptedException, IOException {
|
|
// 1. 先检查视频是否有字幕流
|
if (!hasSubtitleStream(inputPath)) {
|
logger.warn("视频文件没有字幕流: {}", inputPath);
|
return false;
|
}
|
|
// 2. 构建FFmpeg命令
|
String[] ffmpegCommand = {
|
"ffmpeg",
|
"-i", inputPath,
|
"-map", "0:s:0", // 提取第一条字幕流
|
"-c:s", "srt", // 强制转换为SRT格式
|
"-y", // 覆盖已存在文件
|
srtPath
|
};
|
|
try {
|
Process process = new ProcessBuilder(ffmpegCommand)
|
.redirectErrorStream(true) // 合并错误流和标准输出
|
.start();
|
|
// 读取输出
|
try (BufferedReader reader = new BufferedReader(
|
new InputStreamReader(process.getInputStream()))) {
|
String line;
|
while ((line = reader.readLine()) != null) {
|
logger.debug("FFmpeg输出: {}", line);
|
}
|
}
|
|
int exitCode = process.waitFor();
|
if (exitCode == 0) {
|
logger.info("成功提取SRT字幕到: {}", srtPath);
|
return true;
|
} else {
|
logger.error("提取SRT字幕失败,退出码: {}", exitCode);
|
return false;
|
}
|
} catch (IOException | InterruptedException e) {
|
logger.error("提取SRT字幕时出错: {}", e.getMessage());
|
throw e;
|
}
|
}
|
|
/**
|
* 检查视频是否包含字幕流
|
* @param inputPath
|
* @return
|
* @throws IOException
|
*/
|
private static boolean hasSubtitleStream(String inputPath) throws IOException {
|
String[] probeCommand = {
|
"ffprobe",
|
"-v", "error",
|
"-select_streams", "s",
|
"-show_entries", "stream=codec_type",
|
"-of", "csv=p=0",
|
inputPath
|
};
|
|
Process process = new ProcessBuilder(probeCommand).start();
|
try (BufferedReader reader = new BufferedReader(
|
new InputStreamReader(process.getInputStream()))) {
|
|
return reader.readLine() != null; // 有输出表示有字幕流
|
}
|
}
|
|
/**
|
* 根据输入的路径获取输出路径
|
* @param inputPath
|
* @return
|
*/
|
public static String getOutputPathByInputPath(String inputPath){
|
// 获取输入文件的父目录和文件名(不带扩展名)
|
Path path = Paths.get(inputPath);
|
String parentDir = path.getParent().toString();
|
String fileName = path.getFileName().toString();
|
String baseName = fileName.substring(0, fileName.lastIndexOf('.'));
|
|
// 创建以MP4文件名命名的子目录
|
String outputDir = parentDir + File.separator + baseName + File.separator;
|
|
// 确保输出目录存在
|
File file = new File(outputDir);
|
if (!file.exists()){
|
// 目录不存在则创建
|
file.mkdirs();
|
}
|
return outputDir;
|
}
|
|
/**
|
* 根据输入的路径获取输出路径
|
* @param inputPath
|
* @return
|
*/
|
public static String getFileBaseNamePathByInputPath(String inputPath){
|
// 获取输入文件的父目录和文件名(不带扩展名)
|
Path path = Paths.get(inputPath);
|
String fileName = path.getFileName().toString();
|
String baseName = fileName.substring(0, fileName.lastIndexOf('.'));
|
return baseName;
|
}
|
|
/**
|
* mp4 视频转 jpg 图片
|
* @param inputVideoPath 视频输入路径
|
* @param frameInterval 图片帧保持间隔,每多少帧保存一次
|
* @return
|
* @throws IOException
|
* @throws InterruptedException
|
*/
|
public static boolean mp4ToJpg(String inputVideoPath,int frameInterval) throws IOException, InterruptedException {
|
// 设置 图片 保存的目录
|
String outputDir = getOutputPathByInputPath(inputVideoPath);
|
String outputImagePattern = outputDir + "frame_%04d.jpg";
|
// 转换生成图片
|
boolean flag = extractFrames(inputVideoPath, outputImagePattern, frameInterval);
|
// 返回处理结果
|
return flag;
|
}
|
|
/**
|
* mp4 视频转 jpg 图片-优化版本
|
* @param inputVideoPath 视频输入路径
|
* @param frameInterval 图片帧保持间隔,每多少帧保存一次
|
* @return
|
* @throws IOException
|
* @throws InterruptedException
|
*/
|
public static boolean mp4ToJpgRestriction(String inputVideoPath,int frameInterval) throws IOException, InterruptedException {
|
// 设置 图片 保存的目录
|
String outputDir = getOutputPathByInputPath(inputVideoPath);
|
String outputImagePattern = outputDir + "frame_%04d.jpg";
|
// 转换生成图片
|
boolean flag = extractFramesRestriction(inputVideoPath, outputImagePattern, frameInterval);
|
// 返回处理结果
|
return flag;
|
}
|
|
/**
|
* 从 mp4 视频获取 srt 文件
|
* @param inputVideoPath 视频输入路径
|
* @return
|
* @throws InterruptedException
|
* @throws IOException
|
*/
|
public static boolean mp4ToCreateStr(String inputVideoPath) throws InterruptedException, IOException {
|
// 设置 srt 保存的目录
|
String outputDir = getOutputPathByInputPath(inputVideoPath);
|
String strPath = outputDir + "out.srt";
|
// 转换生成 srt 文件
|
boolean flag = extractSrt(inputVideoPath, strPath);
|
// 返回结果
|
return flag;
|
}
|
|
|
// public static void main(String[] args) throws InterruptedException, IOException {
|
// String cc = "E:\\temp\\video\\倾斜视频\\DJI_20250723102452_0001_V"; // 输入视频文件路径
|
//// boolean b = mp4ToJpg(inputVideoPath,30);
|
//// boolean b1 = mp4ToCreateStr(inputVideoPath);
|
// boolean b2 = hasJpegFileNIO(cc);
|
// System.out.println("b2 = " + b2);
|
// }
|
}
|