需要添加的jMAVEN依赖
本地需先安装ffmpeg官网地址-
https://ffmpeg.org/download.html#build-windows
下载完成后配置全局环境
配置对应的bin路径随机打开cmd文件
<!-- 音视频 -->
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-all-deps</artifactId>
<version>3.0.1</version>
<exclusions>
<!-- 排除windows 32位系统 -->
<exclusion>
<groupId>ws.schild</groupId>
<artifactId>jave-nativebin-win32</artifactId>
</exclusion>
<!-- 排除linux 32位系统 -->
<exclusion>
<groupId>ws.schild</groupId>
<artifactId>jave-nativebin-linux32</artifactId>
</exclusion>
<!-- 排除Mac系统-->
<exclusion>
<groupId>ws.schild</groupId>
<artifactId>jave-nativebin-osx64</artifactId>
</exclusion>
</exclusions>
</dependency>
ffmpeg工具类
package com.diyuan.xfarm.business.vedioTest;
import com.xfarm.common.core.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.util.StringUtils;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.info.MultimediaInfo;
import ws.schild.jave.info.VideoSize;
import ws.schild.jave.process.ProcessWrapper;
import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/**
* ffmpeg工具类
*
* @since 2024/1/22
*/
@Slf4j
public class FfmpegUtil {
public static void main(String[] args) throws Exception {
VideoSize videoSize = new VideoSize(480, 800);
System.out.println(GetCompressSize(videoSize, 720, 480));
}
/**
* 通过本地路径获取多媒体文件信息(宽,高,时长,编码等)
*
* @param filePath 文件路径
* @return MultimediaInfo 媒体对象,包含 (宽,高,时长,编码等)
*/
public static MultimediaInfo GetMediaInfo(String filePath) {
try {
return new MultimediaObject(new File(filePath)).getInfo();
} catch (EncoderException e) {
log.error("获取媒体信息异常!", e);
}
return null;
}
/**
* 修改视频分辨率
* 修改分辨率超过原视频,不能提高画质
* 标清SD(Standard Definition) 480p 640x480 720x480
* 高清 HD(High Definition) 720p 960x720 1280x720
* 1080p 1440x1080 1920x1080
* 超高清UHD(Ultra High Definition) 4k 4096×3112 4096*2160
*
* @param inputPath 视频来源地址
* @param outputPath 输出视频地址
* @param width 宽度
* @param height 高度
*/
public static boolean ChangeScale(String inputPath, String outputPath, int width, int height) {
ProcessWrapper ffmpeg = null;
try {
if (new File(outputPath).exists()) {
log.error("目标文件已存在,outputPath:{}", outputPath);
return false;
}
ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(inputPath);
ffmpeg.addArgument("-vf");
ffmpeg.addArgument("scale=w=" + width + ":h=" + height);
ffmpeg.addArgument("-c:a");
ffmpeg.addArgument("copy");
ffmpeg.addArgument(outputPath);
ffmpeg.execute();
//等待完成
String errorMsg = WaitFfmpegFinish(ffmpeg);
if (StringUtils.hasLength(errorMsg)) {
log.error("修改视频分辨率失败,文件:" + inputPath, errorMsg);
return false;
}
return true;
} catch (Exception e) {
log.error("修改视频分辨率异常", e);
} finally {
if (ffmpeg != null) {
ffmpeg.destroy();
}
}
return false;
}
/**
* 调整视频质量
*
* @param inputPath 输入视频
* @param outputPath 输出视频
* @param qualityLevel 质量等级,0-51,0最高
*/
public static boolean ChangeQuality(String inputPath, String outputPath, int qualityLevel) {
ProcessWrapper ffmpeg = null;
try {
if (new File(outputPath).exists()) {
log.error("目标文件已存在,outputPath:{}", outputPath);
return false;
}
ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(inputPath);
ffmpeg.addArgument("-q:v");
ffmpeg.addArgument(qualityLevel + "");
ffmpeg.addArgument("-c:a");
ffmpeg.addArgument("copy");
ffmpeg.addArgument(outputPath);
ffmpeg.execute();
//等待完成
String errorMsg = WaitFfmpegFinish(ffmpeg);
if (StringUtils.hasLength(errorMsg)) {
log.error("修改视频视频质量失败,文件:" + inputPath, errorMsg);
return false;
}
return true;
} catch (Exception e) {
log.error("调整视频质量异常", e);
} finally {
if (ffmpeg != null) {
ffmpeg.destroy();
}
}
return false;
}
/**
* 复制视频片段
*
* @param inputPath 视频来源地址
* @param outputPath 输出视频地址
* @param startTime 开始时间,01:02:03在视频的第1小时第2分钟第3秒处
* @param endTime 结束时间,格式同startTime
*/
public static boolean CopyVideo(String inputPath, String outputPath, String startTime, String endTime) {
ProcessWrapper ffmpeg = null;
try {
if (new File(outputPath).exists()) {
log.error("目标文件已存在,outputPath:{}", outputPath);
return false;
}
ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(inputPath);
ffmpeg.addArgument("-ss");
ffmpeg.addArgument(startTime);
ffmpeg.addArgument("-to");
ffmpeg.addArgument(endTime);
ffmpeg.addArgument("-c:v");
ffmpeg.addArgument("copy");
ffmpeg.addArgument("-c:a");
ffmpeg.addArgument("copy");
ffmpeg.addArgument(outputPath);
//执行
ffmpeg.execute();
//等待完成
String errorMsg = WaitFfmpegFinish(ffmpeg);
if (StringUtils.hasLength(errorMsg)) {
log.error("复制视频失败,文件:" + inputPath, errorMsg);
return false;
}
return true;
} catch (Exception e) {
log.error("复制视频片段异常", e);
} finally {
if (ffmpeg != null) {
ffmpeg.destroy();
}
}
return false;
}
/**
* 格式转换
*
* @param inputPath 视频来源地址
* @param outputPath 输出视频地址
*/
public static boolean TransferFormat(String inputPath, String outputPath) {
ProcessWrapper ffmpeg = null;
try {
if (new File(outputPath).exists()) {
log.error("目标文件已存在,outputPath:{}", outputPath);
return false;
}
ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(inputPath);
ffmpeg.addArgument("-c");
ffmpeg.addArgument("copy");
ffmpeg.addArgument(outputPath);
//执行
ffmpeg.execute();
//等待完成
String errorMsg = WaitFfmpegFinish(ffmpeg);
if (StringUtils.hasLength(errorMsg)) {
log.error("转换视频格式失败,文件:" + inputPath, errorMsg);
return false;
}
return true;
} catch (Exception e) {
log.error("转换视频格式异常", e);
} finally {
if (ffmpeg != null) {
ffmpeg.destroy();
}
}
return false;
}
/**
* 生成视频第多少秒的缩略图
*/
public static boolean GenerateThumbnail(String inputPath, int second, String outputPath) {
ProcessWrapper ffmpeg = null;
try {
File target = new File(outputPath);
if (target.exists()) {
log.error("目标文件已存在,outputPath:{}", outputPath);
return false;
}
ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(inputPath);
ffmpeg.addArgument("-ss");//第多少秒的视频画面
ffmpeg.addArgument("" + second);
ffmpeg.addArgument("-frames:v");//第多少秒的视频画面
ffmpeg.addArgument("1");
ffmpeg.addArgument(outputPath);
ffmpeg.execute();
//等待完成
String errorMsg = WaitFfmpegFinish(ffmpeg);
if (StringUtils.hasLength(errorMsg)) {
log.error("获取缩略图失败,文件:" + inputPath, errorMsg);
return false;
}
return true;
} catch (Exception e) {
log.error("获取视频缩略图异常", e);
} finally {
if (ffmpeg != null) {
ffmpeg.destroy();
}
}
return false;
}
/**
* 抽取视频的指定时间间隔内的帧画面并保存为图片
*
* @param inputPath 输入视频路径
* @param outputPath 帧画面保存路径的前缀
* @param interval 抽取帧的时间间隔 (秒)
* @return 是否成功
*/
public static boolean extractFrames(String inputPath, String outputPath, int interval) {
ProcessWrapper ffmpeg = null;
try {
// 检查目标文件夹是否存在,不存在则创建
File targetDir = new File(outputPath).getParentFile();
if (!targetDir.exists() && !targetDir.mkdirs()) {
log.error("目标文件夹创建失败, outputPath:{}", outputPath);
return false;
}
ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(inputPath);
ffmpeg.addArgument("-vf");
ffmpeg.addArgument("fps=1/" + interval);
ffmpeg.addArgument(outputPath + "%d.png"); // 保存路径带上编号
ffmpeg.execute();
// 等待完成
String errorMsg = WaitFfmpegFinish(ffmpeg);
if (StringUtils.hasLength(errorMsg)) {
log.error("抽取视频帧失败, 文件:" + inputPath, errorMsg);
return false;
}
return true;
} catch (Exception e) {
log.error("抽取视频帧异常", e);
return false;
} finally {
if (ffmpeg != null) {
ffmpeg.destroy();
}
}
}
/**
* 抽取视频的指定时间间隔内的帧画面并返回图像字节数据列表
*
* @param videoBytes 输入视频字节数据
* @param interval 抽取帧的时间间隔 (秒)
* @return 图像字节数据列表
*/
public static List<byte[]> extractFrames(byte[] videoBytes, int interval) {
ProcessWrapper ffmpeg = null;
File tempInputFile = null;
File tempOutputDir = null;
List<byte[]> framesByteList = new ArrayList<>();
try {
// 创建一个临时输入视频文件
tempInputFile = File.createTempFile("tempInput", ".mp4");
try (FileOutputStream fos = new FileOutputStream(tempInputFile)) {
fos.write(videoBytes);
}
// 创建一个临时输出目录
tempOutputDir = new File(System.getProperty("java.io.tmpdir"), "framesOutput");
if (!tempOutputDir.exists() && !tempOutputDir.mkdirs()) {
log.error("目标文件夹创建失败, outputDir:{}", tempOutputDir.getAbsolutePath());
return null;
}
ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(tempInputFile.getAbsolutePath());
ffmpeg.addArgument("-vf");
ffmpeg.addArgument("fps=1/" + interval);
ffmpeg.addArgument(new File(tempOutputDir, "%d.png").getAbsolutePath()); // 保存路径带上编号
ffmpeg.execute();
// 等待完成
String errorMsg = WaitFfmpegFinish(ffmpeg);
if (StringUtils.hasLength(errorMsg)) {
log.error("抽取视频帧失败, 文件:" + tempInputFile.getAbsolutePath(), errorMsg);
throw new ServiceException("抽取视频帧失败");
}
// 读取输出图片文件为字节数组并添加到列表中
File[] frameFiles = tempOutputDir.listFiles();
if (frameFiles != null) {
for (File frameFile : frameFiles) {
if (frameFile.isFile() && frameFile.getName().endsWith(".png")) {
try (InputStream is = new FileInputStream(frameFile)) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IOUtils.copy(is, baos);
framesByteList.add(baos.toByteArray());
}
}
}
}
return framesByteList;
} catch (Exception e) {
log.error("抽取视频帧异常", e);
return null;
} finally {
if (ffmpeg != null) {
ffmpeg.destroy();
}
if (tempInputFile != null && tempInputFile.exists()) {
tempInputFile.delete();
}
// 清理输出临时目录及文件
if (tempOutputDir != null && tempOutputDir.exists()) {
for (File file : tempOutputDir.listFiles()) {
if (file.isFile()) {
file.delete();
}
}
tempOutputDir.delete();
}
}
}
/**
* 抽取视频的第一帧画面并返回图片字节数据
*
* @param videoInputBytes 输入视频字节数据
* @return 返回帧画面的字节数据
*/
public static byte[] extractFirstFrame(byte[] videoInputBytes) {
ProcessWrapper ffmpeg = null;
File tempInputFile = null;
File tempOutputFile = null;
String outputNewPath = null;
try {
// 创建一个临时输入视频文件
tempInputFile = File.createTempFile("tempInput", ".mp4");
try (FileOutputStream fos = new FileOutputStream(tempInputFile)) {
fos.write(videoInputBytes);
}
// 创建一个临时输出文件
ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(tempInputFile.getAbsolutePath());
ffmpeg.addArgument("-vf");
ffmpeg.addArgument("select=eq(n\\,0)");
ffmpeg.addArgument("-vsync");
ffmpeg.addArgument("vfr");
// 输出格式和质量控制
ffmpeg.addArgument("-q:v");
ffmpeg.addArgument("1"); // 质量参数,范围是1-31,1是最高质量
// 这里需要按照路径随机生成新的文件
outputNewPath = generateNewFilePath(tempInputFile.getAbsolutePath(), ".png");
ffmpeg.addArgument(outputNewPath);
ffmpeg.execute();
// 等待完成
String errorMsg = WaitFfmpegFinish(ffmpeg);
if (StringUtils.hasLength(errorMsg)) {
log.error("抽取视频第一帧失败, 文件:" + tempInputFile.getAbsolutePath(), errorMsg);
throw new ServiceException("抽取视频第一帧失败");
}
// 读取输出文件的字节数据
return readFileToByteArray(outputNewPath);
} catch (Exception e) {
log.error("抽取视频第一帧异常", e);
throw new ServiceException("抽取视频第一帧异常");
} finally {
if (ffmpeg != null) {
ffmpeg.destroy();
}
if (tempInputFile != null) {
tempInputFile.delete();
}
if (tempOutputFile != null) {
tempOutputFile.delete();
}
// 根据路径删除生成新的临时文件
if (outputNewPath != null) {
deleteFile(outputNewPath);
}
}
}
/**
* 抽取视频的第一帧画面并保存为图片
*
* @param inputPath 输入视频路径
* @param outputPath 帧画面保存路径
* @return 是否成功
*/
public static boolean extractFirstFrame(String inputPath, String outputPath) {
ProcessWrapper ffmpeg = null;
try {
// 检查目标文件是否已经存在
File target = new File(outputPath);
if (target.exists()) {
log.error("目标文件已存在, outputPath:{}", outputPath);
return false;
}
ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(inputPath);
ffmpeg.addArgument("-vf");
ffmpeg.addArgument("select=eq(n\\,0)");
ffmpeg.addArgument("-vsync");
ffmpeg.addArgument("vfr");
// 输出格式和质量控制
ffmpeg.addArgument("-q:v");
ffmpeg.addArgument("1"); // 质量参数,范围是1-31,1是最高质量
ffmpeg.addArgument(outputPath);
ffmpeg.execute();
// 等待完成
String errorMsg = WaitFfmpegFinish(ffmpeg);
if (StringUtils.hasLength(errorMsg)) {
log.error("抽取视频第一帧失败, 文件:" + inputPath, errorMsg);
return false;
}
return true;
} catch (Exception e) {
log.error("抽取视频第一帧异常", e);
return false;
} finally {
if (ffmpeg != null) {
ffmpeg.destroy();
}
}
}
/**
* 获取ffmpeg默认路径
*/
public static String GetDefaultFFMPEGPath() {
return new File(System.getProperty("java.io.tmpdir"), "jave/").getAbsolutePath();
}
/**
* 等待命令执行完成
*
* @return 成功返回null, 否则返回错误信息
*/
private static String WaitFfmpegFinish(ProcessWrapper processWrapper) throws Exception {
//存储执行错误信息
var errorSb = new StringBuilder();
//ffmpeg的执行过程打印在errorStream中
try (BufferedReader br = new BufferedReader(new InputStreamReader(processWrapper.getErrorStream()))) {
String processInfo;
while ((processInfo = br.readLine()) != null) {
errorSb.append(processInfo).append("\r\n");
}
//执行完成后,获取处理退出编码
int processExitCode = processWrapper.getProcessExitCode();
if (processExitCode != 0) {//非0执行失败,返回执行过程信息
return errorSb.toString();
}
} catch (Exception e) {
log.error("WaitFfmpegFinish异常!", e);
return errorSb.toString();
}
return null;
}
/**
* 获取按照指定宽、高比压缩的视频大小
* 宽度和高度必须是2的倍数
*/
public static VideoSize GetCompressSize(VideoSize oldVideoSize, int weight, int height) {
assert oldVideoSize != null;
int oldWidth = oldVideoSize.getWidth();
int oldHeight = oldVideoSize.getHeight();
double ratio = (double) oldWidth / oldHeight;
if (ratio > 1.0) {//宽屏视频,根据高度缩小
if (oldHeight <= height) {//原高度比预期小,直接返回
return oldVideoSize;
}
int newWeight = (oldWidth * height) / oldHeight;
if (newWeight % 2 != 0) {
newWeight++;
}
return new VideoSize(newWeight, height);
} else {//窄屏视频,根据宽度缩小
if (oldWidth <= weight) {//原宽带度比预期小,直接返回
return oldVideoSize;
}
int newHeight = (oldHeight * weight) / oldWidth;
if (newHeight % 2 != 0) {
newHeight++;
}
return new VideoSize(weight, newHeight);
}
}
//判断视频大小是否相等
public static boolean Equal(VideoSize videoSize1, VideoSize videoSize2) {
if (videoSize1 == null && videoSize2 == null) {
return true;
} else if (videoSize1 == null || videoSize2 == null) {
return false;
} else {
return Objects.equals(videoSize1.getHeight(), videoSize2.getHeight())
&& Objects.equals(videoSize1.getWidth(), videoSize2.getWidth());
}
}
/**
* 修改视频分辨率
*
* @param inputPath 输入视频路径
* @param outputPath 输出视频路径
* @param width 宽度
* @param height 高度
* @return 是否成功
*/
public static boolean AddVideoSize(String inputPath, String outputPath, int width, int height) {
ProcessWrapper ffmpeg = null;
try {
if (new File(outputPath).exists()) {
log.error("目标文件已存在,outputPath:{}", outputPath);
return false;
}
ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(inputPath);
ffmpeg.addArgument("-vf");
// 使用scale和pad滤镜来添加黑边
String padFilter = String.format("scale=w=%d:h=%d:force_original_aspect_ratio=decrease,pad=w=%d:h=%d:x=(%d-iw)/2:y=(%d-ih)/2:color=black", width, height, width, height, width, height);
ffmpeg.addArgument(padFilter);
ffmpeg.addArgument("-c:a");
ffmpeg.addArgument("copy");
ffmpeg.addArgument(outputPath);
ffmpeg.execute();
//等待完成
String errorMsg = WaitFfmpegFinish(ffmpeg);
if (StringUtils.hasLength(errorMsg)) {
log.error("修改视频分辨率失败,文件:" + inputPath, errorMsg);
return false;
}
return true;
} catch (Exception e) {
log.error("修改视频分辨率异常", e);
} finally {
if (ffmpeg != null) {
ffmpeg.destroy();
}
}
return false;
}
/**
* 修改视频分辨率并返回视频字节数据
*
* @param videoBytes 输入视频字节数据
* @param width 目标宽度
* @param height 目标高度
* @return 返回视频字节数据
*/
public static byte[] modifyResolutionVideo(byte[] videoBytes, int width, int height) {
ProcessWrapper ffmpeg = null;
File tempInputFile = null;
String outputNewPath = null;
try {
// 创建一个临时输入视频文件
tempInputFile = File.createTempFile("tempInput", ".mp4");
try (FileOutputStream fos = new FileOutputStream(tempInputFile)) {
fos.write(videoBytes);
}
// 使用ffmpeg处理视频
ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(tempInputFile.getAbsolutePath());
ffmpeg.addArgument("-vf");
// 使用scale和pad滤镜来添加黑边
String padFilter = String.format("scale=w=%d:h=%d:force_original_aspect_ratio=decrease,pad=w=%d:h=%d:x=(%d-iw)/2:y=(%d-ih)/2:color=black",
width, height, width, height, width, height);
ffmpeg.addArgument(padFilter);
ffmpeg.addArgument("-c:a");
ffmpeg.addArgument("copy");
// 生成新的文件路径
outputNewPath = generateNewFilePath(tempInputFile.getAbsolutePath(), ".mp4");
ffmpeg.addArgument(outputNewPath);
ffmpeg.execute();
// 等待ffmpeg完成
String errorMsg = WaitFfmpegFinish(ffmpeg);
if (StringUtils.hasLength(errorMsg)) {
log.error("修改视频分辨率失败:" + tempInputFile.getAbsolutePath(), errorMsg);
throw new ServiceException("修改视频分辨率失败");
}
// 读取输出文件为字节数组
return readFileToByteArray(outputNewPath);
} catch (Exception e) {
log.error("修改视频分辨率异常", e);
throw new ServiceException("修改视频分辨率失败");
} finally {
if (ffmpeg != null) {
ffmpeg.destroy();
}
if (tempInputFile != null) {
tempInputFile.delete();
}
// 根据路径删除生成新的临时文件
if (outputNewPath != null) {
deleteFile(outputNewPath);
}
}
}
/**
* 在视频中添加文本
*
* @param inputPath 输入视频文件的路径
* @param outputPath 输出视频文件的路径
* @param text 添加到视频中的文本
* @param x 文本在视频中的X坐标
* @param y 文本在视频中的Y坐标
* @return 如果操作成功返回true,否则返回false
*/
public static boolean addTextToVideo(String inputPath, String outputPath, String text, int x, int y, byte[] artBytes, String color, int fontSize) {
ProcessWrapper ffmpeg = null;
File tempFontFile = null;
try {
if (new File(outputPath).exists()) {
log.error("目标文件已存在, outputPath:{}", outputPath);
return false;
}
// 创建一个临时字体文件
tempFontFile = File.createTempFile("tempFont", ".ttc");
System.out.println("tempFontFilePath:" + tempFontFile.getAbsolutePath());
try (FileOutputStream fos = new FileOutputStream(tempFontFile)) {
fos.write(artBytes);
}
// 转换视频访问的本地路径
String convertedPath = convertPath(tempFontFile.getPath());
ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(inputPath);
ffmpeg.addArgument("-vf");
String drawTextArg = String.format(
"drawtext=text='%s':fontfile='%s':fontcolor=%s:fontsize=%d:x=%d:y=%d",
text, convertedPath, color, fontSize, x, y
);
// 添加参数到 ffmpeg
ffmpeg.addArgument(drawTextArg);
ffmpeg.addArgument(outputPath);
ffmpeg.execute();
String errorMsg = WaitFfmpegFinish(ffmpeg);
if (StringUtils.hasLength(errorMsg)) {
log.error("添加文字失败:" + inputPath, errorMsg);
return false;
}
return true;
} catch (Exception e) {
log.error("添加文字失败:", e);
return false;
} finally {
if (ffmpeg != null) {
ffmpeg.destroy();
}
}
}
/**
* 添加文字到视频中
*
* @param videoPressTextDTO 视频添加文字请求参数
* @return 返回视频字节数据
*/
public static byte[] addTextToVideo(VideoPressTextDTO videoPressTextDTO) {
ProcessWrapper ffmpeg = null;
File tempInputFile = null;
File tempFontFile = null;
String outputNewPath = null;
try {
// 创建一个临时输入视频文件
tempInputFile = File.createTempFile("tempInput", ".mp4");
try (FileOutputStream fos = new FileOutputStream(tempInputFile)) {
fos.write(videoPressTextDTO.getVideoInputBytes());
}
// 创建一个临时字体文件
tempFontFile = File.createTempFile("tempFont", ".ttc");
try (FileOutputStream fos = new FileOutputStream(tempFontFile)) {
fos.write(videoPressTextDTO.getArtFontBytes());
}
// 转换字体文件路径
ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(tempInputFile.getAbsolutePath());
ffmpeg.addArgument("-vf");
String drawTextArg = String.format(
"drawtext=text='%s':fontfile='%s':fontcolor=%s:fontsize=%d:x=%d:y=%d",
videoPressTextDTO.getText(),
convertPath(tempFontFile.getPath()),
videoPressTextDTO.getColor(),
videoPressTextDTO.getTextSize(),
videoPressTextDTO.getX(),
videoPressTextDTO.getY()
);
ffmpeg.addArgument(drawTextArg);
// 这里需要按照路径随机生成新的文件
outputNewPath = generateNewFilePath(tempInputFile.getAbsolutePath(), ".mp4");
ffmpeg.addArgument(outputNewPath);
ffmpeg.execute();
String errorMsg = WaitFfmpegFinish(ffmpeg);
if (StringUtils.hasLength(errorMsg)) {
log.error("添加文字失败:" + tempInputFile.getAbsolutePath(), errorMsg);
throw new ServiceException("添加文字失败");
}
return readFileToByteArray(outputNewPath);
} catch (Exception e) {
log.error("添加文字失败:", e);
throw new ServiceException("添加文字失败");
} finally {
if (ffmpeg != null) {
ffmpeg.destroy();
}
if (tempInputFile != null) {
tempInputFile.delete();
}
if (tempFontFile != null) {
tempFontFile.delete();
}
// 根据路径删除生成新的临时文件
if (outputNewPath != null) {
deleteFile(outputNewPath);
}
}
}
/**
* 根据路径删除文件
*
* @param filePath 要删除的文件路径
* @return 是否成功删除文件
* @throws IOException 如果删除过程中出现I/O错误
*/
public static boolean deleteFile(String filePath) {
Path path = Paths.get(filePath);
try {
Files.delete(path);
return true; // 文件成功删除
} catch (Exception e) {
log.error("删除文件失败:", e);
throw new ServiceException("删除文件失败");
}
}
/**
* 根据路径转换成
*
* @param filePath
* @return
*/
public static byte[] readFileToByteArray(String filePath) {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = bis.read(buffer, 0, buffer.length)) != -1) {
baos.write(buffer, 0, bytesRead);
}
return baos.toByteArray();
} catch (Exception e) {
log.error("读取文件失败:", e);
throw new RuntimeException("读取文件失败");
}
}
/**
* 根据路径随机生成新的MP4文件路径
*
* @param originalPath 原始文件路径
* @return 新的文件路径
*/
public static String generateNewFilePath(String originalPath, String suffix) {
Path path = Paths.get(originalPath);
String randomFileName = "tempOutput" + UUID.randomUUID().toString().replace("-", "") + suffix;
return path.getParent().resolve(randomFileName).toString();
}
/**
* 添加水印并返回视频字节数据
*
* @param videoPressImageDTO 视频添加水印请求参数
* @return 添加水印并返回视频字节数据
*/
public static byte[] addWatermark(VideoPressImageDTO videoPressImageDTO) {
ProcessWrapper ffmpeg = null;
File tempInputFile = null;
File tempWaterMarkFile = null;
String outputNewPath = null;
try {
// 创建临时输入视频文件
tempInputFile = File.createTempFile("tempInputVideo", ".mp4");
try (FileOutputStream fos = new FileOutputStream(tempInputFile)) {
fos.write(videoPressImageDTO.getVideoInputBytes());
}
// 创建临时水印文件
tempWaterMarkFile = File.createTempFile("tempWaterMark", ".png");
try (FileOutputStream fos = new FileOutputStream(tempWaterMarkFile)) {
fos.write(videoPressImageDTO.getWaterMarkImageBytes());
}
// 执行 ffmpeg 命令
ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(tempInputFile.getAbsolutePath());
ffmpeg.addArgument("-i");
ffmpeg.addArgument(tempWaterMarkFile.getAbsolutePath());
ffmpeg.addArgument("-filter_complex");
ffmpeg.addArgument("overlay=" + videoPressImageDTO.getX() + ":" + videoPressImageDTO.getY());
// 这里需要按照路径随机生成新的文件
outputNewPath = generateNewFilePath(tempInputFile.getAbsolutePath(), ".mp4");
ffmpeg.addArgument(outputNewPath);
ffmpeg.execute();
// 等待完成
String errorMsg = WaitFfmpegFinish(ffmpeg);
if (StringUtils.hasLength(errorMsg)) {
log.error("视频添加水印失败,错误信息: " + errorMsg);
return null;
}
// 读取输出文件为字节数组
return readFileToByteArray(outputNewPath);
} catch (Exception e) {
log.error("视频添加水印异常", e);
return null;
} finally {
if (ffmpeg != null) {
ffmpeg.destroy();
}
if (tempInputFile != null) {
tempInputFile.delete();
}
if (tempWaterMarkFile != null) {
tempWaterMarkFile.delete();
}
// 根据路径删除生成新的临时文件
if (outputNewPath != null) {
deleteFile(outputNewPath);
}
}
}
/**
* 添加水印
*
* @param inputPath 输入视频路径
* @param waterMarkPath 水印文件路径
* @param x 距离左上角水平距离
* @param y 距离左上角垂直距离
* @param outputPath 输出视频路径
*/
public static boolean addWatermark(String inputPath, String waterMarkPath,
int x, int y, String outputPath) {
ProcessWrapper ffmpeg = null;
try {
File target = new File(outputPath);
if (target.exists()) {
log.error("目标文件已存在,outputPath:{}", outputPath);
return false;
}
ffmpeg = new DefaultFFMPEGLocator().createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(inputPath);
ffmpeg.addArgument("-i");
ffmpeg.addArgument(waterMarkPath);
ffmpeg.addArgument("-filter_complex");
ffmpeg.addArgument("overlay=" + x + ":" + y);
ffmpeg.addArgument(outputPath);
ffmpeg.execute();
//等待完成
String errorMsg = WaitFfmpegFinish(ffmpeg);
if (StringUtils.hasLength(errorMsg)) {
log.error("视频添加水印失败,文件:" + inputPath, errorMsg);
return false;
}
return true;
} catch (Exception e) {
log.error("视频添加水印异常", e);
return false;
} finally {
if (ffmpeg != null) {
ffmpeg.destroy();
}
}
}
/**
* 转换成视频合成可以访问的地址反斜杠
*
* @param path
* @return
*/
public static String convertPath(String path) {
// 将反斜杠替换为双反斜杠
String convertedPath = path.replace("\\", "\\\\");
// 如果存在冒号,则在冒号前添加反斜杠
convertedPath = convertedPath.replace(":", "\\:");
return convertedPath;
}
}