doum
3 小时以前 ce44d803b73a65b2cc31db5bcc662139029463d3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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";
    }
}