|
@@ -75,31 +75,66 @@ public class VideoUtil {
|
|
|
* 提取缩略图
|
|
* 提取缩略图
|
|
|
*/
|
|
*/
|
|
|
public static void extractFirstFrame(String videoPath, String outputImagePath) throws IOException, InterruptedException {
|
|
public static void extractFirstFrame(String videoPath, String outputImagePath) throws IOException, InterruptedException {
|
|
|
|
|
+ File videoFile = new File(videoPath);
|
|
|
|
|
+ if (!videoFile.exists()) {
|
|
|
|
|
+ throw new IOException("视频文件不存在: " + videoPath);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ File outputFile = new File(outputImagePath);
|
|
|
|
|
+ File outputDir = outputFile.getParentFile();
|
|
|
|
|
+ if (outputDir != null && !outputDir.exists() && !outputDir.mkdirs()) {
|
|
|
|
|
+ throw new IOException("创建缩略图输出目录失败: " + outputDir.getAbsolutePath());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
String[] command = {
|
|
String[] command = {
|
|
|
- "ffmpeg",
|
|
|
|
|
- "-ss", "00:00:01.000", // 精准定位到第1秒
|
|
|
|
|
- "-i", videoPath, // 输入视频路径
|
|
|
|
|
- "-vframes", "1", // 只提取1帧
|
|
|
|
|
- "-q:v", "2", // 质量控制(2=高质量,范围1-31)
|
|
|
|
|
- "-y", // 覆盖输出文件
|
|
|
|
|
- outputImagePath // 输出图片路径(如 cover.jpg)
|
|
|
|
|
|
|
+ resolveFfmpegCommand(),
|
|
|
|
|
+ "-i", videoPath,
|
|
|
|
|
+ "-frames:v", "1",
|
|
|
|
|
+ "-f", "image2",
|
|
|
|
|
+ "-update", "1",
|
|
|
|
|
+ "-q:v", "10",
|
|
|
|
|
+ "-y",
|
|
|
|
|
+ outputImagePath
|
|
|
};
|
|
};
|
|
|
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
|
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
|
|
- processBuilder.redirectErrorStream(true); // 将标准错误和标准输出合并
|
|
|
|
|
|
|
+ processBuilder.redirectErrorStream(true);
|
|
|
Process process = processBuilder.start();
|
|
Process process = processBuilder.start();
|
|
|
-// process.waitFor();
|
|
|
|
|
|
|
|
|
|
- // 处理进程的标准输出和错误流
|
|
|
|
|
|
|
+ StringBuilder output = new StringBuilder();
|
|
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
|
|
String line;
|
|
String line;
|
|
|
while ((line = reader.readLine()) != null) {
|
|
while ((line = reader.readLine()) != null) {
|
|
|
- log.info(line); // 输出到控制台或日志
|
|
|
|
|
|
|
+ output.append(line).append(System.lineSeparator());
|
|
|
|
|
+ log.info(line);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
int exitCode = process.waitFor();
|
|
int exitCode = process.waitFor();
|
|
|
- if (exitCode != 0 && exitCode != 1) {
|
|
|
|
|
- throw new RuntimeException("FFmpeg 执行失败,退出代码:" + exitCode);
|
|
|
|
|
|
|
+ if (exitCode != 0) {
|
|
|
|
|
+ log.error("FFmpeg 提取缩略图失败, exitCode={}, videoPath={}, outputPath={}, output={}",
|
|
|
|
|
+ exitCode, videoPath, outputImagePath, output);
|
|
|
|
|
+ throw new RuntimeException("FFmpeg 执行失败,退出代码:" + exitCode + ",输出:" + output);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!outputFile.exists() || outputFile.length() == 0) {
|
|
|
|
|
+ throw new RuntimeException("FFmpeg 执行完成但未生成缩略图: " + outputImagePath);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ private static String resolveFfmpegCommand() {
|
|
|
|
|
+ if (isWindows()) {
|
|
|
|
|
+ String configuredPath = System.getenv("FFMPEG_PATH");
|
|
|
|
|
+ if (configuredPath != null && !configuredPath.trim().isEmpty()) {
|
|
|
|
|
+ File ffmpeg = new File(configuredPath.trim());
|
|
|
|
|
+ if (ffmpeg.exists()) {
|
|
|
|
|
+ return ffmpeg.getAbsolutePath();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return "ffmpeg.exe";
|
|
|
|
|
+ }
|
|
|
|
|
+ return "ffmpeg";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static boolean isWindows() {
|
|
|
|
|
+ return System.getProperty("os.name", "").toLowerCase().contains("win");
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|