javacodeadmin/ruoyi-system/src/main/java/com/ruoyi/system/utils/FFmpegUtils.java

349 lines
12 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.ruoyi.system.utils;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.ArrayList;
/**
* FFmpeg工具类
* 用于音频文件合并等操作
*
* @author ruoyi
*/
public class FFmpegUtils {
private static final Logger logger = LoggerFactory.getLogger(FFmpegUtils.class);
/**
* 查找FFmpeg路径
*/
private static String findFFmpegPath() {
// 首先尝试项目内部的FFmpeg
String projectFFmpegPath = getProjectFFmpegPath();
if (projectFFmpegPath != null && isExecutable(projectFFmpegPath)) {
logger.info("使用项目内部FFmpeg: {}", projectFFmpegPath);
return projectFFmpegPath;
}
// 如果项目内部没有尝试系统安装的FFmpeg
String[] systemPaths = {
"ffmpeg",
"C:\\ffmpeg\\bin\\ffmpeg.exe",
"C:\\Program Files\\ffmpeg\\bin\\ffmpeg.exe",
"D:\\ffmpeg\\bin\\ffmpeg.exe",
"/usr/bin/ffmpeg",
"/usr/local/bin/ffmpeg"
};
for (String path : systemPaths) {
if (isExecutable(path)) {
logger.info("使用系统FFmpeg: {}", path);
return path;
}
}
logger.error("未找到可用的FFmpeg");
return null;
}
/**
* 获取项目内部的FFmpeg路径
*/
private static String getProjectFFmpegPath() {
try {
// 获取项目资源目录中的FFmpeg路径
String resourcePath = "/ffmpeg/ffmpeg.exe";
java.net.URL resourceUrl = FFmpegUtils.class.getResource(resourcePath);
if (resourceUrl != null) {
// 如果是jar包中的资源需要提取到临时目录
if (resourceUrl.getProtocol().equals("jar")) {
return extractFFmpegFromJar();
} else {
// 如果是文件系统中的资源,直接返回路径
return new java.io.File(resourceUrl.toURI()).getAbsolutePath();
}
}
// 尝试从classpath中查找
String classpathPath = System.getProperty("user.dir") + "/ruoyi-system/src/main/resources/ffmpeg/ffmpeg.exe";
java.io.File classpathFile = new java.io.File(classpathPath);
if (classpathFile.exists()) {
return classpathFile.getAbsolutePath();
}
} catch (Exception e) {
logger.error("获取项目内部FFmpeg路径失败", e);
}
return null;
}
/**
* 从jar包中提取FFmpeg到临时目录
*/
private static String extractFFmpegFromJar() {
try {
// 创建临时目录
java.io.File tempDir = java.io.File.createTempFile("ffmpeg", "");
tempDir.delete();
tempDir.mkdirs();
// 提取FFmpeg
String ffmpegPath = tempDir.getAbsolutePath() + "/ffmpeg.exe";
java.io.File ffmpegFile = new java.io.File(ffmpegPath);
// 从jar包中复制文件
java.io.InputStream inputStream = FFmpegUtils.class.getResourceAsStream("/ffmpeg/ffmpeg.exe");
if (inputStream != null) {
java.io.FileOutputStream outputStream = new java.io.FileOutputStream(ffmpegFile);
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
inputStream.close();
outputStream.close();
// 设置可执行权限
ffmpegFile.setExecutable(true);
logger.info("从jar包提取FFmpeg到: {}", ffmpegPath);
return ffmpegPath;
}
} catch (Exception e) {
logger.error("从jar包提取FFmpeg失败", e);
}
return null;
}
/**
* 检查文件是否可执行
*/
private static boolean isExecutable(String path) {
try {
ProcessBuilder pb = new ProcessBuilder(path, "-version");
Process process = pb.start();
int exitCode = process.waitFor();
return exitCode == 0;
} catch (Exception e) {
return false;
}
}
/**
* 合并音频文件
*
* @param inputFiles 输入文件列表
* @param outputFile 输出文件路径
* @return 是否成功
*/
public static boolean mergeAudioFiles(List<String> inputFiles, String outputFile) {
try {
logger.info("开始合并音频文件,输入文件数量: {}, 输出文件: {}", inputFiles.size(), outputFile);
// 检查输入文件
for (String inputFile : inputFiles) {
if (!validateAudioFile(inputFile)) {
logger.error("输入文件验证失败: {}", inputFile);
return false;
}
}
// 创建输出目录
File outputDir = new File(outputFile).getParentFile();
if (!outputDir.exists()) {
outputDir.mkdirs();
}
// 使用系统命令方式合并
return mergeWithSystemCommand(inputFiles, outputFile);
} catch (Exception e) {
logger.error("合并音频文件失败", e);
return false;
}
}
/**
* 使用系统命令方式合并
*/
private static boolean mergeWithSystemCommand(List<String> inputFiles, String outputFile) throws IOException {
// 创建输入文件列表
Path inputListFile = Files.createTempFile("input_list", ".txt");
try {
// 写入文件列表
StringBuilder content = new StringBuilder();
for (String inputFile : inputFiles) {
content.append("file '").append(inputFile).append("'\n");
logger.info("添加输入文件: {}", inputFile);
}
Files.write(inputListFile, content.toString().getBytes());
// 记录输入文件列表内容
logger.info("输入文件列表内容:\n{}", content.toString());
// 构建系统命令
String ffmpegPath = findFFmpegPath();
if (ffmpegPath == null) {
logger.error("未找到可用的FFmpeg");
return false;
}
// 构建FFmpeg命令
List<String> command = new ArrayList<>();
command.add(ffmpegPath);
command.add("-f");
command.add("concat");
command.add("-safe");
command.add("0");
command.add("-i");
command.add(inputListFile.toString());
command.add("-c");
command.add("copy");
command.add(outputFile);
ProcessBuilder pb = new ProcessBuilder(command);
// 记录完整的FFmpeg命令
logger.info("执行FFmpeg命令: {}", String.join(" ", command));
// 设置工作目录
pb.directory(new File(System.getProperty("user.dir")));
// 合并错误和标准输出
pb.redirectErrorStream(true);
// 执行命令
Process process = pb.start();
// 读取输出
java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream())
);
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
int exitCode;
try {
exitCode = process.waitFor();
} catch (InterruptedException e) {
logger.error("FFmpeg进程被中断", e);
Thread.currentThread().interrupt();
return false;
}
if (exitCode != 0) {
logger.error("FFmpeg合并失败退出码: {}, 输出: {}", exitCode, output.toString());
// 分析错误原因
String errorOutput = output.toString();
if (errorOutput.contains("No such file or directory")) {
logger.error("错误原因: 输入文件不存在或路径错误");
} else if (errorOutput.contains("Permission denied")) {
logger.error("错误原因: 权限不足,无法读取输入文件或写入输出文件");
} else if (errorOutput.contains("Invalid data found")) {
logger.error("错误原因: 输入文件格式无效或损坏");
} else if (errorOutput.contains("No such file or directory")) {
logger.error("错误原因: 输出目录不存在或无法创建");
} else {
logger.error("错误原因: 未知错误请检查FFmpeg输出信息");
}
return false;
}
// 验证输出文件
File outputFileObj = new File(outputFile);
if (outputFileObj.exists() && outputFileObj.length() > 0) {
logger.info("音频文件合并成功: {}", outputFile);
return true;
} else {
logger.error("合并后的文件不存在或为空: {}", outputFile);
return false;
}
} finally {
// 清理临时文件
Files.deleteIfExists(inputListFile);
}
}
/**
* 检查FFmpeg是否可用
*/
public static boolean isFFmpegAvailable() {
return findFFmpegPath() != null;
}
/**
* 验证音频文件格式
*/
private static boolean validateAudioFile(String filePath) {
try {
File file = new File(filePath);
if (!file.exists()) {
logger.error("文件不存在: {}", filePath);
return false;
}
if (!file.canRead()) {
logger.error("文件无法读取: {}", filePath);
return false;
}
// 检查文件扩展名
String fileName = file.getName().toLowerCase();
if (!fileName.endsWith(".mp3") && !fileName.endsWith(".wav") &&
!fileName.endsWith(".m4a") && !fileName.endsWith(".aac")) {
logger.warn("文件格式可能不支持: {}", filePath);
}
// 检查文件大小
long fileSize = file.length();
if (fileSize == 0) {
logger.error("文件为空: {}", filePath);
return false;
}
logger.info("文件验证通过: {} (大小: {} bytes)", filePath, fileSize);
return true;
} catch (Exception e) {
logger.error("验证文件失败: {}", filePath, e);
return false;
}
}
/**
* 获取FFmpeg版本信息
*/
public static String getFFmpegVersion() {
try {
String ffmpegPath = findFFmpegPath();
if (ffmpegPath != null) {
ProcessBuilder pb = new ProcessBuilder(ffmpegPath, "-version");
Process process = pb.start();
java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream())
);
return reader.readLine();
}
} catch (Exception e) {
logger.error("获取FFmpeg版本失败", e);
}
return "FFmpeg未安装";
}
}