java操作ffmpeg工具类实现添加水印、字幕、修改分辨率等实现。字节返回,本地输出

需要添加的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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值