package com.doumee.service.business.collection;
|
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.commons.lang3.StringUtils;
|
|
import java.io.BufferedReader;
|
import java.io.File;
|
import java.io.InputStreamReader;
|
import java.nio.charset.StandardCharsets;
|
import java.util.Arrays;
|
import java.util.List;
|
import java.util.Locale;
|
import java.util.concurrent.TimeUnit;
|
|
/**
|
* 从视频指定时刻截取 JPEG 帧。
|
*/
|
@Slf4j
|
public final class MediaFrameUtil {
|
|
private static final long SNAPSHOT_TIMEOUT_MINUTES = 10;
|
|
private MediaFrameUtil() {
|
}
|
|
public static double probeDurationSec(String ffmpegPath, File source) {
|
if (source == null || !source.exists() || source.length() <= 0) {
|
return 0;
|
}
|
String ffprobe = resolveFfprobe(ffmpegPath);
|
List<String> command = Arrays.asList(
|
ffprobe,
|
"-v", "error",
|
"-show_entries", "format=duration",
|
"-of", "default=noprint_wrappers=1:nokey=1",
|
source.getAbsolutePath()
|
);
|
try {
|
ProcessBuilder builder = new ProcessBuilder(command);
|
builder.redirectErrorStream(true);
|
Process process = builder.start();
|
String output = readStream(process.getInputStream());
|
boolean finished = process.waitFor(2, TimeUnit.MINUTES);
|
if (!finished) {
|
process.destroyForcibly();
|
return 0;
|
}
|
if (process.exitValue() != 0 || StringUtils.isBlank(output)) {
|
return 0;
|
}
|
return Double.parseDouble(output.trim());
|
} catch (Exception e) {
|
log.warn("ffprobe 读取时长失败: {}", e.getMessage());
|
return 0;
|
}
|
}
|
|
public static boolean extractFrame(String ffmpegPath, File source, File target, double second) {
|
if (source == null || !source.exists() || source.length() <= 0) {
|
return false;
|
}
|
if (second < 0) {
|
second = 0;
|
}
|
String ffmpeg = resolveExecutable(ffmpegPath, "ffmpeg");
|
String sec = formatSeconds(second);
|
List<String> command = Arrays.asList(
|
ffmpeg,
|
"-y",
|
"-ss", sec,
|
"-i", source.getAbsolutePath(),
|
"-frames:v", "1",
|
"-q:v", "2",
|
target.getAbsolutePath()
|
);
|
try {
|
ProcessBuilder builder = new ProcessBuilder(command);
|
builder.redirectErrorStream(true);
|
Process process = builder.start();
|
readStream(process.getInputStream());
|
boolean finished = process.waitFor(SNAPSHOT_TIMEOUT_MINUTES, TimeUnit.MINUTES);
|
if (!finished) {
|
process.destroyForcibly();
|
log.error("FFmpeg 截帧超时 source={} sec={}", source.getAbsolutePath(), sec);
|
return false;
|
}
|
if (process.exitValue() != 0) {
|
log.error("FFmpeg 截帧失败 exitCode={} source={} sec={}", process.exitValue(), source.getAbsolutePath(), sec);
|
return false;
|
}
|
return target.exists() && target.length() > 0;
|
} catch (Exception e) {
|
log.error("FFmpeg 截帧异常 source={} sec={}: {}", source.getAbsolutePath(), sec, e.getMessage());
|
return false;
|
}
|
}
|
|
private static String formatSeconds(double second) {
|
if (second <= 0) {
|
return "0";
|
}
|
return String.format(Locale.US, "%.3f", second);
|
}
|
|
private static String readStream(java.io.InputStream in) throws Exception {
|
StringBuilder sb = new StringBuilder();
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
|
String line;
|
while ((line = reader.readLine()) != null) {
|
sb.append(line).append('\n');
|
}
|
}
|
return sb.toString();
|
}
|
|
private static String resolveExecutable(String configuredPath, String defaultName) {
|
if (StringUtils.isNotBlank(configuredPath)) {
|
return configuredPath.trim();
|
}
|
return defaultName;
|
}
|
|
private static String resolveFfprobe(String ffmpegPath) {
|
if (StringUtils.isBlank(ffmpegPath)) {
|
return "ffprobe";
|
}
|
String path = ffmpegPath.trim();
|
if (path.contains("/") || path.contains("\\")) {
|
int slash = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
|
if (slash >= 0) {
|
String dir = path.substring(0, slash + 1);
|
String name = path.substring(slash + 1);
|
if (name.toLowerCase().endsWith(".exe")) {
|
return dir + name.replaceAll("(?i)ffmpeg\\.exe$", "ffprobe.exe");
|
}
|
return dir + name.replaceAll("(?i)ffmpeg$", "ffprobe");
|
}
|
return path.replaceAll("(?i)ffmpeg", "ffprobe");
|
}
|
return "ffprobe";
|
}
|
}
|