|
@@ -10,10 +10,7 @@ import java.io.*;
|
|
|
import java.security.InvalidKeyException;
|
|
import java.security.InvalidKeyException;
|
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
import java.text.SimpleDateFormat;
|
|
import java.text.SimpleDateFormat;
|
|
|
-import java.util.ArrayList;
|
|
|
|
|
-import java.util.Date;
|
|
|
|
|
-import java.util.List;
|
|
|
|
|
-import java.util.UUID;
|
|
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
|
|
|
@Component
|
|
@Component
|
|
|
public class VideoConverter {
|
|
public class VideoConverter {
|
|
@@ -33,7 +30,8 @@ public class VideoConverter {
|
|
|
private final String tempDir = System.getProperty("java.io.tmpdir") + "/minio-converter/";
|
|
private final String tempDir = System.getProperty("java.io.tmpdir") + "/minio-converter/";
|
|
|
|
|
|
|
|
|
|
|
|
|
- public void convertAndUpload(String inputKey, String outputPrefix, String[] formats) throws Exception {
|
|
|
|
|
|
|
+ public Map convertAndUpload(String inputKey, String outputPrefix, String[] formats) throws Exception {
|
|
|
|
|
+ Map result = new HashMap<>();
|
|
|
MinioClient minioClient = MinioClient.builder()
|
|
MinioClient minioClient = MinioClient.builder()
|
|
|
.endpoint(MINIO_ENDPOINT)
|
|
.endpoint(MINIO_ENDPOINT)
|
|
|
.credentials(ACCESS_KEY, SECRET_KEY)
|
|
.credentials(ACCESS_KEY, SECRET_KEY)
|
|
@@ -46,6 +44,7 @@ public class VideoConverter {
|
|
|
// if (!tempDir.exists()) {
|
|
// if (!tempDir.exists()) {
|
|
|
// tempDir.mkdir();
|
|
// tempDir.mkdir();
|
|
|
// }
|
|
// }
|
|
|
|
|
+ String duration="";
|
|
|
String fileName = new File(inputKey).getName();
|
|
String fileName = new File(inputKey).getName();
|
|
|
String uniqueName = UUID.randomUUID() + "_" + fileName;
|
|
String uniqueName = UUID.randomUUID() + "_" + fileName;
|
|
|
File tempFile = new File(tempDir, uniqueName);
|
|
File tempFile = new File(tempDir, uniqueName);
|
|
@@ -55,6 +54,7 @@ public class VideoConverter {
|
|
|
System.out.println("下载文件:" + tempFile);
|
|
System.out.println("下载文件:" + tempFile);
|
|
|
downloadFromMinio(minioClient, BUCKET_NAME, inputKey, tempFile);
|
|
downloadFromMinio(minioClient, BUCKET_NAME, inputKey, tempFile);
|
|
|
filesToDelete.add(tempFile); // 添加到删除列表
|
|
filesToDelete.add(tempFile); // 添加到删除列表
|
|
|
|
|
+ duration=getVideoDurationMillis(tempFile.getAbsolutePath());
|
|
|
// 执行格式转换
|
|
// 执行格式转换
|
|
|
for (String format : formats) {
|
|
for (String format : formats) {
|
|
|
File outputFile = new File(tempDir, outputPrefix + "." + format);
|
|
File outputFile = new File(tempDir, outputPrefix + "." + format);
|
|
@@ -64,6 +64,7 @@ public class VideoConverter {
|
|
|
uploadToMinio(minioClient, BUCKET_NAME, outputFile.getName(), outputFile);
|
|
uploadToMinio(minioClient, BUCKET_NAME, outputFile.getName(), outputFile);
|
|
|
filesToDelete.add(outputFile); // 添加到删除列表
|
|
filesToDelete.add(outputFile); // 添加到删除列表
|
|
|
}
|
|
}
|
|
|
|
|
+ result.put("duration",duration);
|
|
|
} finally {
|
|
} finally {
|
|
|
// 清理临时文件(保留目录结构)
|
|
// 清理临时文件(保留目录结构)
|
|
|
for (File file : filesToDelete) {
|
|
for (File file : filesToDelete) {
|
|
@@ -72,6 +73,45 @@ public class VideoConverter {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Map getDuration(String inputKey, String outputPrefix, String[] formats) throws Exception {
|
|
|
|
|
+ Map result = new HashMap<>();
|
|
|
|
|
+ MinioClient minioClient = MinioClient.builder()
|
|
|
|
|
+ .endpoint(MINIO_ENDPOINT)
|
|
|
|
|
+ .credentials(ACCESS_KEY, SECRET_KEY)
|
|
|
|
|
+ .build();
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ List<File> filesToDelete = new ArrayList<>(); // 待删除文件列表
|
|
|
|
|
+ // 创建临时目录
|
|
|
|
|
+// File tempDir = new File("temp");
|
|
|
|
|
+// if (!tempDir.exists()) {
|
|
|
|
|
+// tempDir.mkdir();
|
|
|
|
|
+// }
|
|
|
|
|
+ String duration="";
|
|
|
|
|
+ String fileName = new File(inputKey).getName();
|
|
|
|
|
+ String uniqueName = UUID.randomUUID() + "_" + fileName;
|
|
|
|
|
+ File tempFile = new File(tempDir, uniqueName);
|
|
|
|
|
+ try {
|
|
|
|
|
+ System.out.println("下载路径:" + tempDir);
|
|
|
|
|
+ System.out.println("下载名称:" + uniqueName);
|
|
|
|
|
+ System.out.println("下载文件:" + tempFile);
|
|
|
|
|
+ downloadFromMinio(minioClient, BUCKET_NAME, inputKey, tempFile);
|
|
|
|
|
+ filesToDelete.add(tempFile); // 添加到删除列表
|
|
|
|
|
+ duration=getVideoDurationMillis(tempFile.getAbsolutePath());
|
|
|
|
|
+
|
|
|
|
|
+ result.put("duration",duration);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ // 清理临时文件(保留目录结构)
|
|
|
|
|
+ for (File file : filesToDelete) {
|
|
|
|
|
+ if (file.exists() && !file.delete()) {
|
|
|
|
|
+ System.err.println("警告:无法删除临时文件 " + file.getAbsolutePath());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// private void convertVideo(String inputPath, String outputPath, String format) throws IOException, InterruptedException {
|
|
// private void convertVideo(String inputPath, String outputPath, String format) throws IOException, InterruptedException {
|
|
@@ -165,6 +205,111 @@ public class VideoConverter {
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取视频文件时长(毫秒)
|
|
|
|
|
+ * @param inputPath 视频文件路径
|
|
|
|
|
+ * @return 时长毫秒的字符串,失败返回 null
|
|
|
|
|
+ */
|
|
|
|
|
+ private String getVideoDurationMillis(String inputPath) {
|
|
|
|
|
+ List<String> command = new ArrayList<>();
|
|
|
|
|
+ command.add(FFMPEG_PATH);
|
|
|
|
|
+ command.add("-i");
|
|
|
|
|
+ command.add(inputPath);
|
|
|
|
|
+ // 不进行实际转码,只探测信息
|
|
|
|
|
+ command.add("-f");
|
|
|
|
|
+ command.add("null");
|
|
|
|
|
+ command.add("-");
|
|
|
|
|
+
|
|
|
|
|
+ ProcessBuilder pb = new ProcessBuilder(command);
|
|
|
|
|
+ // 将错误流重定向到输入流,因为ffmpeg的输出信息在stderr
|
|
|
|
|
+ pb.redirectErrorStream(true);
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ Process process = pb.start();
|
|
|
|
|
+ StringBuilder output = new StringBuilder();
|
|
|
|
|
+
|
|
|
|
|
+ // 读取输出信息
|
|
|
|
|
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
|
|
|
|
+ String line;
|
|
|
|
|
+ while ((line = reader.readLine()) != null) {
|
|
|
|
|
+ output.append(line).append("\n");
|
|
|
|
|
+ // 实时解析包含 Duration 的行,找到就可以提前退出
|
|
|
|
|
+ if (line.contains("Duration:")) {
|
|
|
|
|
+ String durationStr = extractDuration(line);
|
|
|
|
|
+ if (durationStr != null) {
|
|
|
|
|
+ // 等待进程结束(但我们已经拿到需要的信息了)
|
|
|
|
|
+ process.destroy();
|
|
|
|
|
+ return durationStr;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int exitCode = process.waitFor();
|
|
|
|
|
+ if (exitCode != 0) {
|
|
|
|
|
+ System.err.println("FFmpeg探测失败,退出码:" + exitCode);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果上面没找到,尝试从完整输出中再找一次
|
|
|
|
|
+ String durationStr = extractDuration(output.toString());
|
|
|
|
|
+ if (durationStr != null) {
|
|
|
|
|
+ return durationStr;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } catch (IOException | InterruptedException e) {
|
|
|
|
|
+ e.printStackTrace();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从ffmpeg输出行中提取时长并转换为毫秒字符串
|
|
|
|
|
+ */
|
|
|
|
|
+ private String extractDuration(String line) {
|
|
|
|
|
+ // 匹配格式:Duration: 00:01:30.45, start: 0.000000, bitrate: 320 kb/s
|
|
|
|
|
+ if (line.contains("Duration:")) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 提取时间部分 "00:01:30.45"
|
|
|
|
|
+ String[] parts = line.split("Duration:\\s*");
|
|
|
|
|
+ if (parts.length > 1) {
|
|
|
|
|
+ String timePart = parts[1].split(",")[0].trim(); // "00:01:30.45"
|
|
|
|
|
+
|
|
|
|
|
+ // 解析时分秒毫秒
|
|
|
|
|
+ String[] timeParts = timePart.split(":");
|
|
|
|
|
+ if (timeParts.length >= 3) {
|
|
|
|
|
+ int hours = Integer.parseInt(timeParts[0]);
|
|
|
|
|
+ int minutes = Integer.parseInt(timeParts[1]);
|
|
|
|
|
+
|
|
|
|
|
+ // 秒和毫秒可能用点分隔
|
|
|
|
|
+ String[] secondsParts = timeParts[2].split("\\.");
|
|
|
|
|
+ int seconds = Integer.parseInt(secondsParts[0]);
|
|
|
|
|
+ int millis = 0;
|
|
|
|
|
+ if (secondsParts.length > 1) {
|
|
|
|
|
+ // 处理小数秒,可能是 .45 或 .450 等形式
|
|
|
|
|
+ String millisStr = secondsParts[1];
|
|
|
|
|
+ if (millisStr.length() == 1) {
|
|
|
|
|
+ millis = Integer.parseInt(millisStr) * 100;
|
|
|
|
|
+ } else if (millisStr.length() == 2) {
|
|
|
|
|
+ millis = Integer.parseInt(millisStr) * 10;
|
|
|
|
|
+ } else if (millisStr.length() >= 3) {
|
|
|
|
|
+ millis = Integer.parseInt(millisStr.substring(0, 3));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 计算总毫秒数
|
|
|
|
|
+ long totalMillis = (hours * 3600L + minutes * 60L + seconds) * 1000 + millis;
|
|
|
|
|
+ return String.valueOf(totalMillis);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ System.err.println("解析时长失败,行内容: " + line);
|
|
|
|
|
+ e.printStackTrace();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private static String getMimeType(String fileName) {
|
|
private static String getMimeType(String fileName) {
|
|
|
return switch (fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase()) {
|
|
return switch (fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase()) {
|
|
|
case "mp4" -> "video/mp4";
|
|
case "mp4" -> "video/mp4";
|