Bootstrap

FFmpeg截屏、剪切一段视频、或者压缩视频

网上大多是通过命令的显示操作ffmpeg,不太灵活
这里我给出剪切视频的一段代码,缺点是无法精确,比如我想剪切20秒,但是可能剪切好的是23秒。
其中FFmpegFrameRecorder(outputstream)操作流会报错。原因未知。

import cn.hutool.core.io.FileUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.util.MapUtil;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.*;
import org.opencv.video.Video;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.;
import java.security.SecureRandom;
import java.util.
;

@Slf4j
public class VideoUtils {

public static void main(String[] args) throws Exception {

// t1();
cutVideo(new FileInputStream(“C:\Users\Xpdn\Videos\3.mp4”),
new File(“C:\Users\Xpdn\Videos\output.mp4”), 30L, 900L);

// t2();
}

public static File cutVideo(InputStream inputStream, File outFile, long beginSecond, long endSecond) throws Exception {
    FFmpegFrameGrabber grabber = null;
    FFmpegFrameRecorder recorder = null;
    try {
        grabber = new FFmpegFrameGrabber(inputStream);
        grabber.start();
        if (beginSecond < 0 || (grabber.getLengthInTime() / 1000000L) <= endSecond) {
            return null;
        }
        long numFrame = (long) ((endSecond - beginSecond) * grabber.getFrameRate()) * 2; // 120 * 25

// grabber.setVideoTimestamp(beginSecond * 1000000L);
grabber.setVideoFrameNumber((int) (grabber.getFrameRate() * beginSecond)); // 第分钟开始

        recorder = new FFmpegFrameRecorder(outFile, grabber.getImageWidth(), grabber.getImageHeight());
        recorder.setFormat("mp4");
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
        recorder.setVideoBitrate(grabber.getVideoBitrate());
        recorder.setFrameRate(grabber.getFrameRate());
        recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
        recorder.setAudioChannels(2);
        recorder.start();

        Frame frame;
        long num = 0;
        while ((frame = grabber.grabFrame()) != null && (numFrame >= num)) {
            num++;
            // 处理每一帧的图像或音频数据
            recorder.record(frame);
        }
        return outFile;
    }catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (grabber != null) {
                grabber.stop();
                grabber.release();
            }
            if (recorder != null) {
                recorder.stop();
                recorder.release();
            }
        } catch (FFmpegFrameGrabber.Exception e1) {
            e1.printStackTrace();
        }
    }
    return null;
}

我的完整的工具类

package com.ruoyi.file.utils;

import cn.hutool.core.io.FileUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.util.MapUtil;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.*;
import org.opencv.video.Video;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.;
import java.security.SecureRandom;
import java.util.
;

@Slf4j
public class VideoUtils {

public static void main(String[] args) throws Exception {

// t1();
cutVideo(new FileInputStream(“C:\Users\Xpdn\Videos\3.mp4”),
new File(“C:\Users\Xpdn\Videos\output.mp4”), 30L, 900L);

// t2();
}

public static File cutVideo(InputStream inputStream, File outFile, long beginSecond, long endSecond) throws Exception {
    FFmpegFrameGrabber grabber = null;
    FFmpegFrameRecorder recorder = null;
    try {
        grabber = new FFmpegFrameGrabber(inputStream);
        grabber.start();
        if (beginSecond < 0 || (grabber.getLengthInTime() / 1000000L) <= endSecond) {
            return null;
        }
        long numFrame = (long) ((endSecond - beginSecond) * grabber.getFrameRate()) * 2; // 120 * 25

// grabber.setVideoTimestamp(beginSecond * 1000000L);
grabber.setVideoFrameNumber((int) (grabber.getFrameRate() * beginSecond)); // 第1分钟开始

        recorder = new FFmpegFrameRecorder(outFile, grabber.getImageWidth(), grabber.getImageHeight());
        recorder.setFormat("mp4");
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
        recorder.setVideoBitrate(grabber.getVideoBitrate());
        recorder.setFrameRate(grabber.getFrameRate());
        recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
        recorder.setAudioChannels(2);
        recorder.start();

        Frame frame;
        long num = 0;
        while ((frame = grabber.grabFrame()) != null && (numFrame >= num)) {
            num++;
            // 处理每一帧的图像或音频数据
            recorder.record(frame);
        }
        return outFile;
    }catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (grabber != null) {
                grabber.stop();
                grabber.release();
            }
            if (recorder != null) {
                recorder.stop();
                recorder.release();
            }
        } catch (FFmpegFrameGrabber.Exception e1) {
            e1.printStackTrace();
        }
    }
    return null;
}


public static void t2() throws Exception {

// OutputStream outputStream = new FileOutputStream(“C:\Users\Xpdn\Videos\1xxx.mp4”);
OutputStream outputStream = new ByteArrayOutputStream(1024102410);

    FFmpegFrameGrabber grabber = new FFmpegFrameGrabber("C:\\Users\\Xpdn\\Videos\\1.mp4");
    grabber.start();
    long beginSecond = 60;
    long endSecond = 120;
    long numFrame = (long) ((endSecond - beginSecond) * grabber.getFrameRate());// 60 * 25
    grabber.setVideoTimestamp(beginSecond * 1000000L);
    //grabber.setVideoFrameNumber((int) (25 * beginSecond)); // 2分钟开始
    File file = new File("C:\\Users\\Xpdn\\Videos\\1xxx.mp4");
    FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputStream, grabber.getImageWidth(),
            grabber.getImageHeight(), grabber.getAudioChannels());
    recorder.setFormat("mp4");

// recorder.setOption();
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setVideoBitrate(grabber.getVideoBitrate());
recorder.setFrameRate(grabber.getFrameRate());
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
recorder.start();

    Frame frame;

    long num = 0;
    while ((frame = grabber.grabFrame()) != null && (numFrame >= num)) {
        num++;
        // 处理每一帧的图像或音频数据
        recorder.record(frame);
    }

    recorder.stop();
    recorder.release();
}

public static void t1() throws Exception {
    FFmpegFrameGrabber grabber = new FFmpegFrameGrabber("C:\\Users\\Xpdn\\Videos\\VCG42N1340724607.mp4");
    avutil.av_log_set_level(avutil.AV_LOG_ERROR);
    FFmpegLogCallback.set();

    grabber.start();
    int lengthInVideoFrames = grabber.getLengthInVideoFrames();
    long lengthInTime = grabber.getLengthInTime();
    double videoFrameRate = grabber.getVideoFrameRate();
    grabber.setVideoTimestamp(1 * 1000000L);
    grabber.setVideoFrameNumber(25 * 120);
    FileOutputStream outputStream = new FileOutputStream("C:\\\\Users\\\\Xpdn\\\\Videos\\\\VCG42N1340724607xx.mp4");
    FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputStream, grabber.getImageWidth(),
            grabber.getImageHeight(),grabber.getAudioChannels());
    recorder.setFormat("mp4");
    recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);

// recorder.setVideoCodec(grabber.getVideoCodec());
recorder.setAudioCodec(grabber.getAudioCodec());
recorder.setVideoBitrate(grabber.getVideoBitrate());
recorder.setFrameRate(grabber.getFrameRate());
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
recorder.start();

    Frame frame;
    while ((frame = grabber.grabFrame()) != null) {
        // 处理每一帧的图像或音频数据
        recorder.record(frame);
    }

// recorder.stop();
// recorder.release();
}

public static Map<String, InputStream> clipVideo(InputStream inputStream, Map<Long,Long> timePeriod) throws Exception {
    boolean isStart = true;// 该变量建议设置为全局控制变量,用于控制录制结束
    FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputStream);
    grabber.start();


    //好多视频熟悉可以获取后打印 示例几个
    log.info("ImageWidth:" + grabber.getImageWidth());
    log.info("ImageHeight:" + grabber.getImageHeight());
    log.info("AudioChannels:" + grabber.getAudioChannels());
    log.info("Format:" + grabber.getFormat());
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputStream, grabber.getAudioChannels());
    recorder.setFrameRate(grabber.getFrameRate());

// recorder.setAudioBitrate(grabber.getAudioBitrate());
// recorder.setSampleRate(grabber.getSampleRate());
// recorder.setGopSize(2);
recorder.setFormat(grabber.getFormat());
recorder.setAudioCodecName(“aac”);
recorder.setVideoCodec(grabber.getVideoCodec());
Frame f = null;
//如果想截取规定时间段视频 请看系列文章二
while (isStart) {
f = grabber.grabFrame();
recorder.record(f);
}
recorder.stop();
recorder.release();
grabber.stop();
grabber.release();
return null;
}

/**
 * 将视频文件inputStream流截屏
 *
 * @param inputStream
 * @param seconds     截屏的数量以及秒数
 * @return
 */
public static Map<String, InputStream> generateCover(InputStream inputStream, List<Long> seconds) {
    // 生成封面
    HashMap<String, InputStream> hashMap = new HashMap<>();
    FFmpegFrameGrabber grabber = null;
    try {
        // 初始化FFmpegFrameGrabber
        grabber = new FFmpegFrameGrabber(inputStream);
        grabber.start();
        // 获取截取秒数,循环生成封面
        for (Long second : seconds) {
            // 设置封面截取时间
            grabber.setTimestamp(second * 1000000L);
            Frame f = grabber.grabImage();
            Java2DFrameConverter converter = new Java2DFrameConverter();
            BufferedImage bi = converter.getBufferedImage(f);
            // 指定封面保存地址
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            // 生成封面图片
            ImageIO.write(bi, "jpg", os);
            InputStream input = new ByteArrayInputStream(os.toByteArray());
            hashMap.put(String.valueOf(second), input);
        }
        return hashMap;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (grabber != null) {
                grabber.stop();
                grabber.release();
            }
        } catch (FFmpegFrameGrabber.Exception e) {
            e.printStackTrace();
        }
    }
    return null;
}

/**
 * 将视频文件inputStream流截屏,可以指定随机或者平均
 * @param inputStream
 * @param num
 * @param isRandom
 * @return
 */
public static Map<String, InputStream> generateCover(InputStream inputStream, Long num, boolean isRandom) {
    // 生成封面
    HashMap<String, InputStream> hashMap = new HashMap<>();
    FFmpegFrameGrabber grabber = null;
    try {
        // 初始化FFmpegFrameGrabber
        grabber = new FFmpegFrameGrabber(inputStream);
        grabber.start();
        Set<Long> seconds = null;
        if (isRandom) {
            seconds = generateSecondList(grabber, num, true);
        } else {
            seconds = averageSecondList(grabber, num);
        }
        // 获取截取秒数,循环生成封面
        for (Long second : seconds) {
            // 设置封面截取时间
            grabber.setTimestamp(second * 1000000L);
            Frame f = grabber.grabImage();
            Java2DFrameConverter converter = new Java2DFrameConverter();
            BufferedImage bi = converter.getBufferedImage(f);
            // 指定封面保存地址
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            // 生成封面图片
            ImageIO.write(bi, "jpg", os);
            InputStream input = new ByteArrayInputStream(os.toByteArray());
            hashMap.put(String.valueOf(second), input);
        }
        return hashMap;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (grabber != null) {
                grabber.stop();
                grabber.release();
            }
        } catch (FFmpegFrameGrabber.Exception e) {
            e.printStackTrace();
        }
    }
    return null;
}


/**
 * 生成视频封面
 *
 * @param videoFile 视频全路径,目前发现支持mp4、webm,其他的需要自己去尝试
 * @param coverPath
 * @param coverNum  封面数量
 * @param random    是否随机生成封面
 * @return map,其中key是秒数,而value是图片全路径
 **/
public static Map<String, String> generateCover(File videoFile, String coverPath, long coverNum, boolean random, Set<Long> seconds) {
    // 验证视频
    if (!(videoFile != null && videoFile.isFile() && videoFile.exists())) {
        throw new RuntimeException("参数:videoFile 不符合要求");
    }
    // 验证封面数量
    if (coverNum <= 0) {
        throw new RuntimeException("参数:coverNum 必须大于0");
    }
    // 存储封面
    Map<String, String> coverMap = new TreeMap<>();
    // 生成封面
    FFmpegFrameGrabber grabber = null;
    try {
        // 初始化FFmpegFrameGrabber
        grabber = new FFmpegFrameGrabber(videoFile);
        grabber.start();
        // 获取截取秒数
        Set<Long> secondSet = seconds == null ? generateSecondList(grabber, coverNum, random) : seconds;
        // 循环生成封面
        for (Long second : secondSet) {
            // 设置封面截取时间
            grabber.setTimestamp(second * 1000000L);
            Frame f = grabber.grabImage();
            Java2DFrameConverter converter = new Java2DFrameConverter();
            BufferedImage bi = converter.getBufferedImage(f);
            // 指定封面保存地址

// File imgFile = createTempFile(“cover.jpg”);
String videoFileName = videoFile.getName();
File imgFile = new File(coverPath, videoFileName.substring(0, videoFileName.lastIndexOf(“.”)) + “.jpg”);
// 生成封面图片
ImageIO.write(bi, “jpg”, imgFile);
String s = toBase64Img(imgFile);
coverMap.put(“imgBase64”, s);
imgFile.delete();
// 装载封面图片
// coverMap.put(second, imgFile.getAbsolutePath());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (grabber != null) {
grabber.stop();
grabber.release();
}
} catch (FFmpegFrameGrabber.Exception e) {
e.printStackTrace();
}
}

    return coverMap;
}

/**
 * 图片转换成base64格式
 *
 * @param file
 * @return
 */
public static String toBase64Img(File file) {
    FileInputStream fileInputStream = null;
    String s = null;
    try {
        fileInputStream = new FileInputStream(file);
        byte[] encode = Base64.getEncoder().encode(fileInputStream.readAllBytes());
        s = new String(encode);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (fileInputStream != null) {
            try {
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return "data:image/jpg;base64," + s;
}

public static Long getVideoTotalSecond(InputStream inputStream) {
    // 生成封面
    FFmpegFrameGrabber grabber = null;
    try {
        // 初始化FFmpegFrameGrabber
        grabber = new FFmpegFrameGrabber(inputStream);
        grabber.start();
        return grabber.getLengthInTime() / 1000000;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (grabber != null) {
                grabber.stop();
                grabber.release();
            }
        } catch (FFmpegFrameGrabber.Exception e) {
            e.printStackTrace();
        }
    }
    return -1L;
}

/**
 * 随机获取封面截图秒数集合
 *
 * @param grabber  FFmpegFrameGrabber对象
 * @param coverNum 封面数量
 * @param random   是否随机生成封面
 * @return 封面截图秒数集合
 * @author 明快de玄米61
 * @date 2022/12/4 17:51
 **/
@SneakyThrows
private static Set<Long> generateSecondList(FFmpegFrameGrabber grabber, long coverNum, boolean random) {
    // 存储封面截图秒数集合
    Set<Long> secondSet = new TreeSet<>();
    // 获取视频时长秒数;说明:除以1000000可以获取秒数
    long totalVideoSecond = grabber.getLengthInTime() / 1000000;
    // 随机生成截图时间
    if (random) {
        Random secureRandom = SecureRandom.getInstanceStrong();
        for (int i = 1; i <= coverNum; i++) {
            secondSet.add((long) (secureRandom.nextInt((int) totalVideoSecond - 1) + 1));
        }
    } else {
        // 自定义截图时间
        coverNum = totalVideoSecond > coverNum ? coverNum : totalVideoSecond;
        for (long i = 1; i <= coverNum; i++) {
            secondSet.add(i);
        }
    }
    return secondSet;
}

/**
 * 平均获取视频的秒数集合
 *
 * @param grabber
 * @param coverNum
 * @return
 */
private static Set<Long> averageSecondList(FFmpegFrameGrabber grabber, long coverNum) {
    // 存储封面截图秒数集合
    Set<Long> secondSet = new TreeSet<>();
    // 获取视频时长秒数;说明:除以1000000可以获取秒数
    long totalVideoSecond = grabber.getLengthInTime() / 1000000;
    long l = totalVideoSecond / (coverNum + 1); // 平均的秒数间隔
    for (long i = 0; i < coverNum; i++) {
        secondSet.add((i + 1) * l);
    }
    return secondSet;
}

}

;