ffmpeg 是领先的多媒体框架,能够解码、编码、转码、混合、解密、流媒体、过滤音视频文件。

jave2是一个内置了ffmpeg.exe 的jar,并提供了对ffmpeg命令的封装。

使用jave2可以不用先安装配置ffmpeg的环境变量。

注:jave2 版本3.0后, windows 下视频加水印由于:路径问题有bug。

导入依赖:

<!--引入全平台windows 32/64 linux 32/64 下的ffmpeg 可执行程序,实际运行不需要这么多-->
        <!--        <dependency>-->
        <!--            <groupId>ws.schild</groupId>-->
        <!--            <artifactId>jave-all-deps</artifactId>-->
        <!--            <version>3.1.1</version>-->
        <!--        </dependency>-->

        <!--操作ffmpeg命令的jar-->
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-core</artifactId>
            <version>3.1.1</version>
        </dependency>
        <!--windows 引用jar,主要集成 ffmpeg-amd64-3.1.1.exe  -->
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-nativebin-win64</artifactId>
            <version>3.1.1</version>
        </dependency>
        <!--linux 引用jar,主要集成 ffmpeg-amd64-3.1.1 linux下可执行文件  -->
        <dependency>
            <groupId>ws.schild</groupId>
            <artifactId>jave-nativebin-linux64</artifactId>
            <version>3.1.1</version>
        </dependency>

具体代码如下:

import org.apache.commons.lang3.time.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ws.schild.jave.Encoder;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.ScreenExtractor;
import ws.schild.jave.encode.AudioAttributes;
import ws.schild.jave.encode.EncodingAttributes;
import ws.schild.jave.encode.VideoAttributes;
import ws.schild.jave.filtergraphs.OverlayWatermark;
import ws.schild.jave.filters.helpers.OverlayLocation;
import ws.schild.jave.info.AudioInfo;
import ws.schild.jave.info.MultimediaInfo;
import ws.schild.jave.info.VideoSize;

import java.io.File;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

/**
 * 视频处理工具类,利用jave2 jar处理
 * 1:截取视频多张图
 * 2:获取视频时长,格式等信息
 * 3:视频压缩
 * 4:视频添加图片水印
 *
 * @author lin
 * @version 1.0.0
 * @Description
 * @createTime 2021年06月15日 16:32:00
 */
public class VideoJave2Utils {
    static Logger logger = LoggerFactory.getLogger(VideoJave2Utils.class);
    /**
     * 允许的视频格式
     */
    static final String ALLOW_IMG_FORMAT = "avi|wma|rmvb|flash|mp4|mid|3gp|wmv|mpg|mp3|mkv|mpeg|mov|flv";
    /**
     * 水印视频名字拼接
     */
    static final String NEW_VIDOE_WATER_NAME_PRE_STR = "_water";

    /***
     * 压缩视频文件后缀
     */
    static final String NEW_VIDOE_ZIP_NAME_PRE_STR = "_ys";
    /**
     * 视频截图文件名拼接
     * eg:原视频 test.mp4
     * 截图名称 test_sc1.jpg 、test_sc10.jpg
     */
    static final String NEW_SC_NAME_PRE_STR = "_sc";

    /**
     * 视频加图片水印
     * jave2插件     3.0版本后 针对windows版本的路径有bug,导致windows下添加水印命令不成功
     * 但是3.0版本前,没找到添加水印功能,所以不考虑windows或则反编译MovieFilter.escapingPath 方法修改
     * <p>
     * movie='C\\:\\Users\\lin\\Desktop\\usedMarketIcon.png'[watermark];[0:v][watermark]overlay='main_w-overlay_w-10:main_h-overlay_h-10'
     * <p>
     * C\\: 其实只需要一个\
     *
     * @param urlYuan       源视频绝对路径
     * @param targetUrl     水印视频绝对路径
     * @param waterIconPath 水印图文件绝对路径
     * @throws EncoderException
     */
    public static String generateVideoImageWatermark(String urlYuan, String targetUrl, String waterIconPath) throws EncoderException {
        /**如果没有传入生成后的地址,在在源目录下保存生成后的水印视频**/
        if (StringUtils.isBlank(targetUrl)) {
            targetUrl = urlYuan.substring(0, urlYuan.lastIndexOf(".")) + NEW_VIDOE_WATER_NAME_PRE_STR + urlYuan.substring(urlYuan.lastIndexOf("."));
        }
        if (StringUtils.isBlank(waterIconPath)) {
            throw new RuntimeException("水印图地址为空");
        }
        File sourceVideo = new File(urlYuan);
        if (!sourceVideo.exists()) {
            throw new RuntimeException("源视频文件不存在" + sourceVideo.getAbsolutePath());
        }
        File watermark = new File(waterIconPath);
        if (!watermark.exists()) {
            throw new RuntimeException("水印图文件不存在:" + waterIconPath);
        }
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        VideoAttributes vidAttr = new VideoAttributes();
        OverlayWatermark overlayWatermark = new OverlayWatermark(watermark, OverlayLocation.BOTTOM_RIGHT, -10, -10);
        logger.info("overlayWatermark.getExpression()" + overlayWatermark.getExpression());
        vidAttr.addFilter(overlayWatermark);
        AudioAttributes audioAttributes = new AudioAttributes();
        audioAttributes.setCodec(AudioAttributes.DIRECT_STREAM_COPY);
        EncodingAttributes encAttr = new EncodingAttributes().setVideoAttributes(vidAttr).setAudioAttributes(audioAttributes);
        File target = new File(targetUrl);
        new Encoder().encode(new MultimediaObject(sourceVideo), target, encAttr);
        stopWatch.stop();
        logger.info("生成视频图片水印 =" + urlYuan + "水印视频 =" + targetUrl + "耗时=" + stopWatch.getTime() + "毫秒");
        return targetUrl;
    }

    /**
     * 获取视频时长 返回秒
     *
     * @param urlYuan 原视频绝对路径
     * @return
     * @throws EncoderException
     */
    public static int readVideoLength(String urlYuan) throws EncoderException {
        File sourceFile = new File(urlYuan); //
        MultimediaObject multimediaObject = new MultimediaObject(sourceFile);
        MultimediaInfo multimediaInfo = multimediaObject.getInfo();
        long sum = multimediaInfo.getDuration() / 1000; //时长sum单位:秒
        return (int) sum;
    }

    /**
     * 视频压缩
     *
     * @param urlYuan
     * @param targetUrl
     * @return
     * @throws EncoderException
     */
    public static String compressionVideo(String urlYuan, String targetUrl) throws EncoderException {
        // 视频属性设置
        /**
         * 码率:影响体积,与体积成正比:码率越大,体积越大;码率越小,体积越小。
         */
        int audioMaxBitRate = 128000;
        int videoMaxBitRate = 800000;
        /**
         * 采样率
         */
        int maxSamplingRate = 44100;

        /**
         * 帧率(FPS) :影响画面流畅度,与画面流畅度成正比:帧率越大,画面越流畅;帧率越小,画面越有跳动感。
         * 如果码率为变量,则帧率也会影响体积,帧率越高,每秒钟经过的画面越多,需要的码率也越高,体积也越大。
         * 帧率就是在1秒钟时间里传输的图片的帧数,也可以理解为图形处理器每秒钟能够刷新几次。
         * 每秒显示的图片数影响画面流畅度,与画面流畅度成正比:帧率越大,画面越流畅;帧率越小,画面越有跳动感。
         * 由于人类眼睛的特殊生理结构,如果所看画面之帧率高于16的时候,就会认为是连贯的,此现象称之为视觉暂留。
         * 并且当帧速达到一定数值后,再增长的话,人眼也不容易察觉到有明显的流畅度提升了。
         */
        int maxFrameRate = 20;//帧率
        int maxWidth = 1280;//压缩后的视频最大宽度
        if (StringUtils.isBlank(targetUrl)) {
            targetUrl = urlYuan.substring(0, urlYuan.lastIndexOf(".")) + NEW_VIDOE_ZIP_NAME_PRE_STR + maxWidth + urlYuan.substring(urlYuan.lastIndexOf("."));
        }
        File sourceFile = new File(urlYuan);
        File target = new File(targetUrl);
        MultimediaObject multimediaObject = new MultimediaObject(sourceFile);
        MultimediaInfo multimediaInfo = multimediaObject.getInfo();
        AudioInfo audioInfo = multimediaInfo.getAudio();
        double mb = Math.ceil(sourceFile.length() / 1048576);
        int second = (int) multimediaInfo.getDuration() / 1000;
        logger.info("原视频={},大小={}M,时长={}秒", urlYuan, mb, second);
        BigDecimal bd = new BigDecimal(String.format("%.4f", mb / second));
        logger.info("开始压缩视频了--> 视频每秒平均 " + bd + " MB ");
        // 根据视频大小来判断是否需要进行压缩,视频 > 5MB, 或者每秒 > 0.5 MB 才做压缩, 不需要的话可以把判断去掉
        int maxSize = 5;
        boolean temp = mb > maxSize || bd.compareTo(new BigDecimal(0.5)) > 0;
        if (temp) {
            return urlYuan;
        }
        long time = System.currentTimeMillis();
        AudioAttributes audio = new AudioAttributes();
        // 设置通用编码格式10                   audio.setCodec("aac");
        // 设置最大值:比特率越高,清晰度/音质越好
        // 设置音频比特率,单位:b (比特率越高,清晰度/音质越好,当然文件也就越大 128000 = 182kb)
        if (audioInfo.getBitRate() > audioMaxBitRate) {
            audio.setBitRate(new Integer(audioMaxBitRate));
        }

        // 设置重新编码的音频流中使用的声道数(1 =单声道,2 = 双声道(立体声))。如果未设置任何声道值,则编码器将选择默认值 0。
        audio.setChannels(audioInfo.getChannels());
        // 采样率越高声音的还原度越好,文件越大
        // 设置音频采样率,单位:赫兹 hz
        // 设置编码时候的音量值,未设置为0,如果256,则音量值不会改变
        // audio.setVolume(256);
        if (audioInfo.getSamplingRate() > maxSamplingRate) {
            audio.setSamplingRate(maxSamplingRate);
        }

        // 视频编码属性配置
        ws.schild.jave.info.VideoInfo videoInfo = multimediaInfo.getVideo();
        VideoAttributes video = new VideoAttributes();
        video.setCodec("h264");
        //设置音频比特率,单位:b (比特率越高,清晰度/音质越好,当然文件也就越大 800000 = 800kb)
        if (videoInfo.getBitRate() > videoMaxBitRate) {
            video.setBitRate(videoMaxBitRate);
        }

        // 视频帧率:15 f / s  帧率越低,效果越差
        // 设置视频帧率(帧率越低,视频会出现断层,越高让人感觉越连续),视频帧率(Frame rate)是用于测量显示帧数的量度。所谓的测量单位为每秒显示帧数(Frames per Second,简:FPS)或“赫兹”(Hz)。
        if (videoInfo.getFrameRate() > maxFrameRate) {
            video.setFrameRate(maxFrameRate);
        }

        // 限制视频宽高
        int width = videoInfo.getSize().getWidth();
        int height = videoInfo.getSize().getHeight();
        if (width > maxWidth) {
            float rat = (float) width / maxWidth;
            video.setSize(new VideoSize(maxWidth, (int) (height / rat)));
        }

        EncodingAttributes attr = new EncodingAttributes();
        // attr.setOutputFormat("mp4");
        attr.setAudioAttributes(audio);
        attr.setVideoAttributes(video);
        //attr.setEncodingThreads(Runtime.getRuntime().availableProcessors() / 2);
        new Encoder().encode(multimediaObject, target, attr);
        logger.info("压缩视频={},新视频={},耗时={}秒", sourceFile, targetUrl, (System.currentTimeMillis() - time) / 1000);
        return targetUrl;
    }

    /**
     * 将视频时长转换成"00:00:00.000"的字符串格式
     *
     * @param duration 视频时长(单位:秒)
     * @return
     */
    public static String durationFormatToString(BigDecimal duration) {
        BigDecimal nine = BigDecimal.valueOf(9);
        BigDecimal sixty = BigDecimal.valueOf(60);

        BigDecimal second = duration.divideAndRemainder(sixty)[1];
        BigDecimal minute = duration.subtract(second).divide(sixty).divideAndRemainder(sixty)[1];
        BigDecimal hour = duration.subtract(second).divideToIntegralValue(BigDecimal.valueOf(3600));

        String str = "";
        if (hour.intValue() != 0) {
            if (hour.compareTo(nine) > 0) {
                str += hour.intValue() + ":";
            } else {
                str += "0" + hour.intValue() + ":";
            }
        }
        if (minute.compareTo(nine) > 0) {
            str += minute.intValue() + ":";
        } else {
            str += "0" + minute.intValue() + ":";
        }
        if (second.compareTo(nine) > 0) {
            str += second.intValue();
        } else {
            str += "0" + second.intValue();
        }
        return str;
    }

    /**
     * 视频截取
     *
     * @param urlYuan     原视频绝对路径 eg:C:/Users/lin/Desktop/test.mp4
     * @param targetPath  截图路径 eg:C:/Users/lin/Desktop/
     * @param secondsList 截取的秒 列表 eg:[1,10,20] 截取 第1秒,第10秒,第20秒
     * @throws EncoderException
     */
    public static void generateVideoManyScreenImage(String urlYuan, String targetPath, List<Integer> secondsList) throws EncoderException {
        checkIsVideo(urlYuan);
        /**默认同目录下**/
        if (StringUtils.isBlank(targetPath)) {
            targetPath = urlYuan.substring(0, urlYuan.lastIndexOf("."));
        }
        /**默认截取第1秒**/
        if (secondsList == null || secondsList.size() == 0) {
            secondsList = new ArrayList<>();
            secondsList.add(1);
        }
        MultimediaObject multimediaObject = new MultimediaObject(new File(urlYuan));
        ScreenExtractor screenExtractor = new ScreenExtractor();
        for (Integer seconds : secondsList) {
            String targetUrl = targetPath + NEW_SC_NAME_PRE_STR + seconds + ".jpg";
            int width = -1;
            int height = -1;
            long millis = seconds * 1000;
            File outputFile = new File(targetUrl);
            int quality = 1;
            logger.info("原视频 = {},生成截图 = {}", urlYuan, targetUrl);
            screenExtractor.renderOneImage(multimediaObject, width, height, millis, outputFile, quality);
        }
    }


    /**
     * 校验文件是否是视频文件
     *
     * @param urlYuan 视频绝对路径
     */
    public static void checkIsVideo(String urlYuan) {
        if (org.apache.commons.lang3.StringUtils.isBlank(urlYuan)) {
            throw new RuntimeException("源视频路径为空");
        }
        urlYuan = urlYuan.toLowerCase();
        String suffix = urlYuan.substring(urlYuan.lastIndexOf(".") + 1);
        if (!ALLOW_IMG_FORMAT.contains(suffix)) {
            throw new RuntimeException("源视频格式不合法");
        }
    }
}

 

测试代码:

@Test
    public void videoJave2UtilsTest() throws EncoderException {
        String sourceFile = "C:/Users/lin/Desktop/test.mp4";
        String waterMark = "C:/Users/lin/Desktop/usedMarketIcon.png";
        try {
            VideoJave2Utils.generateVideoImageWatermark(sourceFile, null, waterMark);
        } catch (Exception e) {
            e.printStackTrace();
        }
        List<Integer> secondsList = new ArrayList<>();
        secondsList.add(1);
        secondsList.add(10);
        secondsList.add(20);
        VideoJave2Utils.generateVideoManyScreenImage(sourceFile, null, secondsList);
        int seconds = VideoJave2Utils.readVideoLength(sourceFile);
        logger.info("视频时长:" + seconds);
        logger.info("视频时间格式化后时长:" + VideoJave2Utils.durationFormatToString(new BigDecimal(seconds)));
        VideoJave2Utils.compressionVideo(sourceFile, null);
    }

结果

java ppt 清除水印 java实现视频去水印_java视频压缩