|
@@ -1,5 +1,9 @@
|
|
|
package cn.reghao.oss.store.media;
|
|
package cn.reghao.oss.store.media;
|
|
|
|
|
|
|
|
|
|
+import cn.reghao.oss.store.media.handler.ConvertVideoOutputHandler;
|
|
|
|
|
+import cn.reghao.oss.store.media.handler.EmptyHandler;
|
|
|
|
|
+import cn.reghao.oss.store.media.handler.OutputHandler;
|
|
|
|
|
+import cn.reghao.oss.store.media.handler.ShellWrapper;
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
@@ -23,185 +27,6 @@ import java.util.regex.Pattern;
|
|
|
*/
|
|
*/
|
|
|
@Slf4j
|
|
@Slf4j
|
|
|
public class VideoProcessor {
|
|
public class VideoProcessor {
|
|
|
- static String baseDir = "/home/reghao/Downloads/3";
|
|
|
|
|
-
|
|
|
|
|
- static List<KeyFrameInfo> getKeyFrameOffsets(List<String> commands) throws Exception {
|
|
|
|
|
- Process process = new ProcessBuilder(commands).start();
|
|
|
|
|
- // 使用 Jackson 或 Gson 解析 ffprobe 输出的 JSON
|
|
|
|
|
- ObjectMapper mapper = new ObjectMapper();
|
|
|
|
|
- JsonNode root = mapper.readTree(process.getInputStream());
|
|
|
|
|
- JsonNode frames = root.get("frames");
|
|
|
|
|
-
|
|
|
|
|
- List<KeyFrameInfo> keyFrames = new ArrayList<>();
|
|
|
|
|
- if (frames.isArray()) {
|
|
|
|
|
- for (JsonNode frame : frames) {
|
|
|
|
|
- // 只提取 I 帧 (关键帧)
|
|
|
|
|
- if ("I".equals(frame.get("pict_type").asText())) {
|
|
|
|
|
- KeyFrameInfo info = new KeyFrameInfo();
|
|
|
|
|
- info.setOffset(frame.get("pkt_pos").asLong());
|
|
|
|
|
- info.setTimestamp(frame.get("pkt_pts_time").asDouble());
|
|
|
|
|
- keyFrames.add(info);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return keyFrames;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- static void run() throws IOException {
|
|
|
|
|
- String outputPath = "/mnt/nfs/vod/raw/videoId.mp4";
|
|
|
|
|
-
|
|
|
|
|
- // -c copy 表示直接拷贝编码后的数据,不重编码,CPU 占用极低
|
|
|
|
|
- List<String> command = Arrays.asList(
|
|
|
|
|
- "ffmpeg",
|
|
|
|
|
- "-i", "rtmpUrl",
|
|
|
|
|
- "-c", "copy",
|
|
|
|
|
- "-f", "mp4",
|
|
|
|
|
- "-movflags", "+faststart", // 方便后续秒开播放
|
|
|
|
|
- outputPath
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- ProcessBuilder pb = new ProcessBuilder(command);
|
|
|
|
|
- // 同样需要处理 ErrorStream,参考我们之前聊过的“僵尸进程”防御
|
|
|
|
|
- Process process = pb.start();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- static Pattern timePattern = Pattern.compile("time=(\\d{2}:\\d{2}:\\d{2}.\\d{2})");
|
|
|
|
|
- static MafdAnalyzer analyzer = new MafdAnalyzer();
|
|
|
|
|
- static int executeFFmpeg(List<String> commands, double totalSeconds) throws Exception {
|
|
|
|
|
- ProcessBuilder pb = new ProcessBuilder(commands);
|
|
|
|
|
- Process process = pb.start();
|
|
|
|
|
- // 父进程使用两个线程分别读取子进程的 stdout 和 stderr, 也就是子进程会向父进程写数据
|
|
|
|
|
- // FFmpeg 的日志输出在 stderr, Java 程序必须不断读取 process.getErrorStream(), 否则会导致缓冲区满造成进程挂起(Zombie Process)
|
|
|
|
|
- ExecutorService executor = Executors.newFixedThreadPool(2);
|
|
|
|
|
- executor.submit(() -> {
|
|
|
|
|
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
|
|
|
|
- String line;
|
|
|
|
|
- while ((line = reader.readLine()) != null) {
|
|
|
|
|
- log.info("FFmpeg INFO: {}", line);
|
|
|
|
|
- /*if (line.contains("lavfi")) {
|
|
|
|
|
- analyzer.processLine(line);
|
|
|
|
|
- }*/
|
|
|
|
|
- }
|
|
|
|
|
- } catch (IOException e) {
|
|
|
|
|
- log.error("读取标准流异常", e);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- executor.submit(() -> {
|
|
|
|
|
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
|
|
|
|
|
- String line;
|
|
|
|
|
- // BufferedReader 是同步阻塞式的
|
|
|
|
|
- // 有数据: 立即读取并返回
|
|
|
|
|
- // 没数据但流没关: 线程进入等待状态,让出 CPU 资源,直到操作系统通知有新数据到达
|
|
|
|
|
- // 流关闭时(子进程退出): readLine 返回 null, 此时阻塞解除
|
|
|
|
|
- while ((line = reader.readLine()) != null) {
|
|
|
|
|
- // 这里可以解析 line 来获取转码进度(如 time=00:00:10.50)
|
|
|
|
|
- log.error("FFmpeg ERROR: {}", line);
|
|
|
|
|
- /*if (line.contains("lavfi")) {
|
|
|
|
|
- analyzer.processLine(line);
|
|
|
|
|
- }*/
|
|
|
|
|
-
|
|
|
|
|
- /*Matcher matcher = timePattern.matcher(line);
|
|
|
|
|
- if (matcher.find()) {
|
|
|
|
|
- String timeStr = matcher.group(1);
|
|
|
|
|
- double currentSeconds = convertToSeconds(timeStr);
|
|
|
|
|
- double percent = (currentSeconds / totalSeconds) * 100;
|
|
|
|
|
- log.error("FFmpeg ERROR: {}", percent);
|
|
|
|
|
- // 限制频率,防止每帧都发消息给前端(比如每增加 1% 发一次)
|
|
|
|
|
- //updateProgressToFrontend(videoId, percent);
|
|
|
|
|
- }*/
|
|
|
|
|
- }
|
|
|
|
|
- } catch (IOException e) {
|
|
|
|
|
- log.error("读取错误流异常", e);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- // 停止接收新任务,但会把已提交的任务执行完
|
|
|
|
|
- // executor 默认创建的都是非守护
|
|
|
|
|
- executor.shutdown();
|
|
|
|
|
-
|
|
|
|
|
- // 执行 kill -9 会让操作系统直接从内存和 CPU 调度中抹除进程, JVM 进程还没来得及执行下一行指令就被杀死,所以 ShutdownHook 自然无法运行
|
|
|
|
|
- // ShutdownHook 依赖于 JVM 接收到操作系统的信号后的内部处理机制
|
|
|
|
|
- // 子进程默认情况下变成"孤儿进程", 它会立即被 1 号进程(init 或 systemd) 领养
|
|
|
|
|
- // 如果子进程正通过 stdin/stdout 与父进程进行实时通信, 父进程被杀时 pipe 关闭, 子进程在下一次尝试向 pipe 写数据时会收到操作系统的 SIGPIPE 信号, 大多数程序在收到 SIGPIPE 时的默认行为是退出, 但如果子进程忽略了该信号或没有写操作那它依然会继续运行
|
|
|
|
|
- // 如果子进程只是在读,那么它收不到 SIGPIPE 信号, 当子进程尝试从 pipe 读取数据, 而写入端(父进程)被关闭时, 读操作会立即返回 0(表示 EOF, 文件结束符), 读取到 EOF 的结果取决于子进程的代码逻辑
|
|
|
|
|
- // 注册钩子:当 JVM 退出时执行
|
|
|
|
|
- Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
|
|
|
|
- if (process.isAlive()) {
|
|
|
|
|
- process.destroyForcibly();
|
|
|
|
|
- log.info("JVM 退出,已清理残留的 FFmpeg 进程");
|
|
|
|
|
- }
|
|
|
|
|
- }));
|
|
|
|
|
-
|
|
|
|
|
- // 1. 设置强制超时,防止 FFmpeg 陷入无限循环
|
|
|
|
|
- if (!process.waitFor(10, TimeUnit.MINUTES)) {
|
|
|
|
|
- // 超过 2 小时还没转完,直接干掉
|
|
|
|
|
- process.destroyForcibly();
|
|
|
|
|
- }
|
|
|
|
|
- // 2. 确保 Java 退出时,FFmpeg 也跟着死
|
|
|
|
|
- process.descendants().forEach(ProcessHandle::destroyForcibly);
|
|
|
|
|
- // 1. 处理错误流 (stderr) - FFmpeg 的主要输出都在这
|
|
|
|
|
- /*Thread errorThread = new Thread(() -> {
|
|
|
|
|
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
|
|
|
|
|
- String line;
|
|
|
|
|
- while ((line = reader.readLine()) != null) {
|
|
|
|
|
- // 这里可以解析 line 来获取转码进度(如 time=00:00:10.50)
|
|
|
|
|
- log.info("FFmpeg Log: {}", line);
|
|
|
|
|
- }
|
|
|
|
|
- } catch (IOException e) {
|
|
|
|
|
- log.error("读取错误流异常", e);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 2. 处理标准输出流 (stdout) - 预防万一
|
|
|
|
|
- Thread outputThread = new Thread(() -> {
|
|
|
|
|
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
|
|
|
|
- String line;
|
|
|
|
|
- while ((line = reader.readLine()) != null) {
|
|
|
|
|
- log.info("FFmpeg Log: {}", line);
|
|
|
|
|
- }
|
|
|
|
|
- } catch (IOException e) {
|
|
|
|
|
- log.error("读取标准流异常", e);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- errorThread.start();
|
|
|
|
|
- outputThread.start();*/
|
|
|
|
|
-
|
|
|
|
|
- // 3. 等待进程结束
|
|
|
|
|
- int exitCode = process.waitFor();
|
|
|
|
|
- System.out.println("exitCode: " + exitCode);
|
|
|
|
|
-
|
|
|
|
|
- // 确保线程执行完毕
|
|
|
|
|
-// errorThread.join();
|
|
|
|
|
-// outputThread.join();
|
|
|
|
|
- return exitCode;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 将 FFmpeg 的时间字符串 (HH:mm:ss.SS) 转换为总秒数
|
|
|
|
|
- * @param timeStr 例如 "00:02:16.65"
|
|
|
|
|
- * @return 总秒数,例如 136.65
|
|
|
|
|
- */
|
|
|
|
|
- static double convertToSeconds(String timeStr) {
|
|
|
|
|
- if (timeStr == null || !timeStr.contains(":")) {
|
|
|
|
|
- return 0.0;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- String[] parts = timeStr.split(":");
|
|
|
|
|
- if (parts.length != 3) return 0.0;
|
|
|
|
|
-
|
|
|
|
|
- double hours = Double.parseDouble(parts[0]);
|
|
|
|
|
- double minutes = Double.parseDouble(parts[1]);
|
|
|
|
|
- double seconds = Double.parseDouble(parts[2]);
|
|
|
|
|
-
|
|
|
|
|
- // 计算总秒数: 时*3600 + 分*60 + 秒
|
|
|
|
|
- return hours * 3600 + minutes * 60 + seconds;
|
|
|
|
|
- } catch (NumberFormatException e) {
|
|
|
|
|
- // 记录异常日志,防止解析错误导致线程崩溃
|
|
|
|
|
- return 0.0;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
static List<Double> getSceneTimestamps(String videoPath) throws Exception {
|
|
static List<Double> getSceneTimestamps(String videoPath) throws Exception {
|
|
|
List<Double> timestamps = new ArrayList<>();
|
|
List<Double> timestamps = new ArrayList<>();
|
|
|
timestamps.add(0.0); // 默认从0秒开始
|
|
timestamps.add(0.0); // 默认从0秒开始
|
|
@@ -233,7 +58,7 @@ public class VideoProcessor {
|
|
|
|
|
|
|
|
static void splitByScenes(String input, List<Double> points) {
|
|
static void splitByScenes(String input, List<Double> points) {
|
|
|
File file = new File(input);
|
|
File file = new File(input);
|
|
|
- String parent = file.getParent();
|
|
|
|
|
|
|
+ String parentPath = file.getParent();
|
|
|
String filename = file.getName();
|
|
String filename = file.getName();
|
|
|
double total = getVideoTotalSeconds(input);
|
|
double total = getVideoTotalSeconds(input);
|
|
|
for (int i = 0; i < points.size(); i++) {
|
|
for (int i = 0; i < points.size(); i++) {
|
|
@@ -247,66 +72,45 @@ public class VideoProcessor {
|
|
|
// 如果是最后一段,时长由 ffprobe 获取的总长度决定,这里简化演示
|
|
// 如果是最后一段,时长由 ffprobe 获取的总长度决定,这里简化演示
|
|
|
double duration = (i < points.size() - 1) ? (points.get(i + 1) - start) : -1;
|
|
double duration = (i < points.size() - 1) ? (points.get(i + 1) - start) : -1;
|
|
|
|
|
|
|
|
- String output = String.format("%s/%s_part%s.mp4", parent, filename, i);
|
|
|
|
|
|
|
+ String output = String.format("%s/%s_part%s.mp4", parentPath, filename, i);
|
|
|
// 构造精准剪切命令
|
|
// 构造精准剪切命令
|
|
|
- List<String> cmd = new ArrayList<>(Arrays.asList(
|
|
|
|
|
|
|
+ List<String> command = new ArrayList<>(Arrays.asList(
|
|
|
"ffmpeg", "-hide_banner", "-y",
|
|
"ffmpeg", "-hide_banner", "-y",
|
|
|
"-ss", String.valueOf(start),
|
|
"-ss", String.valueOf(start),
|
|
|
"-i", input
|
|
"-i", input
|
|
|
));
|
|
));
|
|
|
if (duration != -1) {
|
|
if (duration != -1) {
|
|
|
- cmd.add("-t"); cmd.add(String.valueOf(duration));
|
|
|
|
|
|
|
+ command.add("-t");
|
|
|
|
|
+ command.add(String.valueOf(duration));
|
|
|
}
|
|
}
|
|
|
- cmd.addAll(Arrays.asList(
|
|
|
|
|
|
|
+ command.addAll(Arrays.asList(
|
|
|
"-c:v", "h264_nvenc",
|
|
"-c:v", "h264_nvenc",
|
|
|
- "-preset", "p4",
|
|
|
|
|
|
|
+ "-rc", "vbr",
|
|
|
"-cq", "24",
|
|
"-cq", "24",
|
|
|
|
|
+ "-qmin", "24",
|
|
|
|
|
+ "-qmax", "24",
|
|
|
|
|
+ "-preset", "p4",
|
|
|
|
|
+ "-pix_fmt", "yuv420p",
|
|
|
"-c:a", "aac",
|
|
"-c:a", "aac",
|
|
|
- "-avoid_negative_ts", "make_zero",
|
|
|
|
|
- "-map_metadata", "-1",
|
|
|
|
|
|
|
+ "-b:a", "128k",
|
|
|
"-movflags", "+faststart",
|
|
"-movflags", "+faststart",
|
|
|
output
|
|
output
|
|
|
));
|
|
));
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- executeFFmpeg(cmd, total0);
|
|
|
|
|
|
|
+ OutputHandler stdoutHandler = new EmptyHandler();
|
|
|
|
|
+ OutputHandler stderrHandler = new ConvertVideoOutputHandler(total0);
|
|
|
|
|
+ int exitCode = ShellWrapper.executeFFmpeg(command, stdoutHandler, stderrHandler);
|
|
|
|
|
+ if (exitCode != 0) {
|
|
|
|
|
+ String errorMsg = "convert video failed";
|
|
|
|
|
+ throw new RuntimeException(errorMsg);
|
|
|
|
|
+ }
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
throw new RuntimeException(e);
|
|
throw new RuntimeException(e);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- static void getMafd(String input) {
|
|
|
|
|
- // 构造精准剪切命令
|
|
|
|
|
- List<String> cmd = new ArrayList<>(Arrays.asList(
|
|
|
|
|
- "ffmpeg", "-i", input,
|
|
|
|
|
- "-filter_complex", "scdet=threshold=10",
|
|
|
|
|
- "-f", "null", "-"
|
|
|
|
|
- ));
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- executeFFmpeg(cmd, 0.0);
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- throw new RuntimeException(e);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- static double calculateAdaptiveThreshold(List<Double> mafdList) {
|
|
|
|
|
- // 1. 计算均值
|
|
|
|
|
- double sum = 0;
|
|
|
|
|
- for (double d : mafdList) sum += d;
|
|
|
|
|
- double mean = sum / mafdList.size();
|
|
|
|
|
-
|
|
|
|
|
- // 2. 计算标准差
|
|
|
|
|
- double varsum = 0;
|
|
|
|
|
- for (double d : mafdList) varsum += Math.pow(d - mean, 2);
|
|
|
|
|
- double stdev = Math.sqrt(varsum / mafdList.size());
|
|
|
|
|
-
|
|
|
|
|
- // 3. 动态返回:底色越乱,门槛越高
|
|
|
|
|
- // 这里的 10.0 是基础门槛,3.5 是波动系数
|
|
|
|
|
- return Math.max(10.0, mean + 3.5 * stdev);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
static double getVideoTotalSeconds(String videoPath) {
|
|
static double getVideoTotalSeconds(String videoPath) {
|
|
|
List<String> cmd = Arrays.asList(
|
|
List<String> cmd = Arrays.asList(
|
|
|
"ffprobe",
|
|
"ffprobe",
|
|
@@ -331,128 +135,14 @@ public class VideoProcessor {
|
|
|
return 0.0;
|
|
return 0.0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- static void convertToWebStandard(File inputFile) {
|
|
|
|
|
- double total = getVideoTotalSeconds(inputFile.getAbsolutePath());
|
|
|
|
|
-
|
|
|
|
|
- List<String> cmd = new ArrayList<>(Arrays.asList(
|
|
|
|
|
- "ffmpeg", "-y",
|
|
|
|
|
- "-i", inputFile.getAbsolutePath()
|
|
|
|
|
- ));
|
|
|
|
|
-
|
|
|
|
|
- String outputPath = inputFile.getAbsolutePath() + ".mp4";
|
|
|
|
|
- cmd.addAll(Arrays.asList(
|
|
|
|
|
- "-c:v", "h264_nvenc",
|
|
|
|
|
- "-preset", "p4",
|
|
|
|
|
- "-cq", "24",
|
|
|
|
|
- "-c:a", "aac",
|
|
|
|
|
- "-avoid_negative_ts", "make_zero",
|
|
|
|
|
- "-map_metadata", "-1",
|
|
|
|
|
- "-movflags", "+faststart",
|
|
|
|
|
- outputPath
|
|
|
|
|
- ));
|
|
|
|
|
-
|
|
|
|
|
- // 基础参数
|
|
|
|
|
- List<String> command = new ArrayList<>();
|
|
|
|
|
- command.add("ffmpeg");
|
|
|
|
|
- command.add("-y");
|
|
|
|
|
- command.add("-hide_banner");
|
|
|
|
|
- command.add("-i");
|
|
|
|
|
- command.add(inputFile.getAbsolutePath());
|
|
|
|
|
- // 视频参数:强制 H.264
|
|
|
|
|
- command.add("-c:v"); command.add("h264_nvenc");
|
|
|
|
|
- command.add("-pix_fmt"); command.add("yuv420p");
|
|
|
|
|
- // 适配 FastStart (Web 播放关键)
|
|
|
|
|
- command.add("-movflags"); command.add("+faststart");
|
|
|
|
|
- // 音频参数
|
|
|
|
|
- command.add("-c:a"); command.add("aac");
|
|
|
|
|
- command.add(outputPath);
|
|
|
|
|
- try {
|
|
|
|
|
- executeFFmpeg(command, total);
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- throw new RuntimeException(e);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- static void transcodeWithEnhanceAndWatermark(String input) {
|
|
|
|
|
- String text = "画质增强预览";
|
|
|
|
|
- String fontPath = "/usr/share/fonts/wenquanyi/wqy-microhei/wqy-microhei.ttc";
|
|
|
|
|
- String filterChain =
|
|
|
|
|
- // 1. 去噪 (hqdn3d)
|
|
|
|
|
- "hqdn3d=2:2:7:7," +
|
|
|
|
|
- // 2. 锐化 (unsharp)
|
|
|
|
|
- "unsharp=5:5:1.0:5:5:1.0," +
|
|
|
|
|
- // 3. 缩放至 720P 并补黑边 (scale + pad)
|
|
|
|
|
- "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2:black," +
|
|
|
|
|
- // 4. 色彩增强 (eq)
|
|
|
|
|
- "eq=contrast=1.1:saturation=1.3," +
|
|
|
|
|
- // 5. 叠加文字水印 (drawtext)
|
|
|
|
|
- "drawtext=text='" + text + "':fontfile='" + fontPath + "':fontsize=24:fontcolor=white@0.5:x=w-tw-40:y=40";
|
|
|
|
|
-
|
|
|
|
|
- String output = String.format("%s.mp4", input);
|
|
|
|
|
- List<String> command = Arrays.asList(
|
|
|
|
|
- "ffmpeg", "-i", input,
|
|
|
|
|
- "-vf", filterChain,
|
|
|
|
|
- "-c:v", "libx264", "-preset", "slow", "-crf", "20",
|
|
|
|
|
- "-c:a", "aac", "-b:a", "128k",
|
|
|
|
|
- "-movflags", "+faststart",
|
|
|
|
|
- "-y", output
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- double total = getVideoTotalSeconds(input);
|
|
|
|
|
- try {
|
|
|
|
|
- executeFFmpeg(command, total);
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- throw new RuntimeException(e);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
public static void main(String[] args) throws Exception {
|
|
|
- String videoPath = "/home/reghao/Downloads/0/abc2.mp4";
|
|
|
|
|
- //videoPath = "/home/reghao/Downloads/start_app.sh";
|
|
|
|
|
- List<String> commands = Arrays.asList(
|
|
|
|
|
- "ffprobe", "-v", "error",
|
|
|
|
|
- "-select_streams", "v",
|
|
|
|
|
- "-show_frames",
|
|
|
|
|
- "-show_entries", "frame=pkt_pos,pkt_pts_time,pict_type",
|
|
|
|
|
- "-of", "json",
|
|
|
|
|
- videoPath
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // 使用 ffprobe 检查是否能读取流信息
|
|
|
|
|
- // 如果文件头破损,命令会直接返回非 0 退出码
|
|
|
|
|
- List<String> cmd = Arrays.asList("ffprobe", "-v", "error", "-show_format", "-show_streams", videoPath);
|
|
|
|
|
-
|
|
|
|
|
- //executeFFmpeg(cmd, 0);
|
|
|
|
|
- //List<KeyFrameInfo> list = getKeyFrameOffsets(commands);
|
|
|
|
|
-
|
|
|
|
|
- String dir = "/home/reghao/disk/2/porn/yyy.待去广告/aaa/";
|
|
|
|
|
|
|
+ String dir = "/home/reghao/disk/3/porn/yyy.待去广告/aaa/1";
|
|
|
for (File file : new File(dir).listFiles()) {
|
|
for (File file : new File(dir).listFiles()) {
|
|
|
String absolutePath = file.getAbsolutePath();
|
|
String absolutePath = file.getAbsolutePath();
|
|
|
long start = System.currentTimeMillis();
|
|
long start = System.currentTimeMillis();
|
|
|
- List<Double> list = getSceneTimestamps(videoPath);
|
|
|
|
|
- log.info("cost {} ms", (System.currentTimeMillis()-start)/1000);
|
|
|
|
|
|
|
+ List<Double> list = getSceneTimestamps(file.getAbsolutePath());
|
|
|
splitByScenes(absolutePath, list);
|
|
splitByScenes(absolutePath, list);
|
|
|
- log.info("cost {} ms", (System.currentTimeMillis()-start)/1000);
|
|
|
|
|
|
|
+ log.info("process {} cost {}s", absolutePath, (System.currentTimeMillis()-start)/1000);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- String videoPath1 = "/home/reghao/Downloads/3/scene_125.mp4";
|
|
|
|
|
- //getMafd(videoPath1);
|
|
|
|
|
-
|
|
|
|
|
- /*VideoMetadataValidator validator = new VideoMetadataValidator();
|
|
|
|
|
- JsonNode meta = validator.getFullMetadata(videoPath);*/
|
|
|
|
|
-
|
|
|
|
|
- // 1. 检查是否伪装
|
|
|
|
|
- /*if (!validator.hasStreamType(meta, "video")) {
|
|
|
|
|
- System.out.println("这不是一个视频文件!");
|
|
|
|
|
- }*/
|
|
|
|
|
- // 2. 漂亮地打印出整个 JSON(调试用)
|
|
|
|
|
- //System.out.println(meta.toPrettyString());
|
|
|
|
|
- // 3. 获取特定的元数据,比如视频的编码规格(Profile)
|
|
|
|
|
- //String profile = meta.path("streams").get(0).path("profile").asText();
|
|
|
|
|
- //System.out.println("视频 Profile: " + profile); // 例如: High 或 Main
|
|
|
|
|
-
|
|
|
|
|
- videoPath = "/home/reghao/Downloads/1/abc1.mp4";
|
|
|
|
|
-// convertToWebStandard(new File(videoPath));
|
|
|
|
|
-// transcodeWithEnhanceAndWatermark(videoPath);
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|