从视频中截取指定帧图片

前言:

我们在很多时候需要对视频文件进行分析,或者对视频产生缩略图。因此视频截取技术必不可少。

从本地文件中读取视频帧

导包

xml 复制代码
 		<dependency>
            <groupId>org.jcodec</groupId>
            <artifactId>jcodec</artifactId>
            <version>0.2.5</version>
        </dependency>
        <dependency>
            <groupId>org.jcodec</groupId>
            <artifactId>jcodec-javase</artifactId>
            <version>0.2.5</version>
        </dependency>
        <!-- http://repo1.maven.org/maven2/commons-io/commons-io/2.6/commons-io-2.6.jar -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>

读取视频帧工具类

java 复制代码
package com.wkl.testdemo.vedio;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.jcodec.api.FrameGrab;
import org.jcodec.api.JCodecException;
import org.jcodec.common.model.Picture;
import org.jcodec.scale.AWTUtil;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.util.UUID;

/**
 * 视频操作工具类
 */
@Slf4j
public class VideoUtils {
 
    /*** 图片格式*/
    private static final String FILE_EXT = "jpg";
 
    /*** 帧数-第几帧*/
    private static final int THUMB_FRAME = 500;
 
    /**
     * 获取指定视频的帧并保存为图片至指定目录
     *
     * @param videoFilePath 源视频文件路径
     * @param frameFilePath 截取帧的图片存放路径
     */
    public static void fetchFrame(String videoFilePath, String frameFilePath) throws Exception {
        File videoFile = new File(videoFilePath);
        File frameFile = new File(frameFilePath);
        getThumbnail(videoFile, frameFile);
    }
 
    /**
     * 获取指定视频的帧并保存为图片至指定目录
     *
     * @param videoFile  源视频文件
     * @param targetFile 截取帧的图片
     */
    public static void fetchFrame(MultipartFile videoFile, File targetFile) throws Exception {
        File file = new File(videoFile.getName());
        FileUtils.copyInputStreamToFile(videoFile.getInputStream(), file);
        getThumbnail(file, targetFile);
    }
 
    /**
     * 获取指定视频的帧并保存为图片至指定目录
     *
     * @param videoFile 源视频文件
     */
    public static File fetchFrame(MultipartFile videoFile) {
        String originalFilename = videoFile.getOriginalFilename();
        File file = new File(originalFilename);
        File targetFile = null;
        try {
            FileUtils.copyInputStreamToFile(videoFile.getInputStream(), file);
 
            int i = originalFilename.lastIndexOf(".");
            String imageName;
 
            if (i > 0) {
                imageName = originalFilename.substring(0, i);
            } else {
                imageName = UUID.randomUUID().toString().replace("-", "");
            }
            imageName = imageName + ".jpg";
            targetFile = new File(imageName);
            getThumbnail(file, targetFile);
        } catch (Exception e) {
            log.error("获取视频指定帧异常:", e);
        } finally {
            if (file.exists()) {
                file.delete();
            }
        }
        log.debug("视频文件 - 帧截取 - 处理结束");
        return targetFile;
    }
 
    /**
     * 获取第一帧缩略图
     *
     * @param videoFile  视频路径
     * @param targetFile 缩略图目标路径
     */
    public static void getThumbnail(File videoFile, File targetFile) {
        try {
            // 根据扩展名创建一个新文件路径
            Picture picture = FrameGrab.getFrameFromFile(videoFile, THUMB_FRAME);
            BufferedImage bufferedImage = AWTUtil.toBufferedImage(picture);
            ImageIO.write(bufferedImage, FILE_EXT, targetFile);
        } catch (IOException | JCodecException e) {
            e.printStackTrace();
            log.error("获取第一帧缩略图异常:", e);
        }
    }
 
    public static void main(String[] args) {
        try {
            long startTime = System.currentTimeMillis();
            getThumbnail(new File("D:\\Videos\\2023112911533869304715.mp4"), new File("D:\\Videos\\test1.jpg"));

            System.out.println("截取图片耗时:" + (System.currentTimeMillis() - startTime));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void saveImage(String imageUrl, String destinationFile) throws IOException {
        URL url = new URL(imageUrl);
        InputStream is = url.openStream();
        OutputStream os = new FileOutputStream(destinationFile);

        byte[] b = new byte[2048];
        int length;

        while ((length = is.read(b)) != -1) {
            os.write(b, 0, length);
        }

        is.close();
        os.close();
    }
 
}

从网络url 读取视频帧-ffmpeg

导包

xml 复制代码
 <!-- 获取视频第一帧依赖 -->
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacpp</artifactId>
            <version>1.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv</artifactId>
            <version>1.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco.javacpp-presets</groupId>
            <artifactId>ffmpeg-platform</artifactId>
            <version>3.4.2-1.4.1</version>
        </dependency>

工具类

java 复制代码
package com.wkl.testdemo.vedio;

import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.Java2DFrameConverter;
 
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
 
/**
 * 视频抽帧工具
 */
@Slf4j
public class VideoFrame {


    //传入包含特定时间的Frame ,读取图片
    public static BufferedImage doExecuteFrame(Frame frame, int index) {
        if (frame == null || frame.image == null) {
            return null;
        }
        Java2DFrameConverter converter = new Java2DFrameConverter();
        BufferedImage bi = converter.getBufferedImage(frame);
        return bi;
    }

    /*
     * @description:  读取视频第n秒的图片帧
     * @author: wangkanglu
     * @date: 2023/12/8 15:05
     * @param: [videoUrl, stepSecond]
     * @return: java.awt.image.BufferedImage
     **/
    public static BufferedImage doExecuteFrameByTime(String videoUrl, Integer stepSecond) {
        FFmpegFrameGrabber ff = new FFmpegFrameGrabber(videoUrl);
        ff.setOption("timeout", "40000000");
        long timestamp =  stepSecond * 1000000L;    //视频是按照微秒计算的,10的6次方分之一秒
        try {
            ff.start();
            ff.setTimestamp(timestamp);
            Frame frame = ff.grabImage();
            if (frame == null || frame.image == null) {
                return null;
            }
            Java2DFrameConverter converter = new Java2DFrameConverter();
            BufferedImage bi = converter.getBufferedImage(frame);
            ff.stop();
            return bi;
        } catch (FrameGrabber.Exception e) {
            throw new RuntimeException(e);
        }

    }
 
    /**
     * 视频文件边下载边抽帧1秒1帧
     *
     * @param videoUrl   网络视频文件URL
     * @param stepSecond 每隔几秒取一帧,默认1s
     * @param count      需要截取的帧个数
     * @return
     */
    public static Map<Integer, BufferedImage> videoUrlIntercept(String videoUrl, Integer stepSecond, Integer count) {
        Map<Integer, BufferedImage> files = new HashMap<>();
        stepSecond = stepSecond == null ? 1 : stepSecond;
        FFmpegFrameGrabber ff = new FFmpegFrameGrabber(videoUrl);
        // 设置超时时间为40秒
        ff.setOption("timeout", "40000000");
//        ff.setOption("user_agent", UserAgent.getUserAgent());
 
        try {
            ff.start();
            long timeLength = ff.getLengthInTime();
            Frame frame = ff.grabImage();
            long startTime = frame.timestamp;
            long timestamp = 0; //视频的当前时长
            int second = 0;  //过了几个时间间隔
            int picNum = 0;//第n张帧
            while (timestamp <= timeLength) {
                log.info("抽取第{}帧,video_url:{}",picNum,videoUrl);
                timestamp = startTime + second * 1000000L;    //视频是按照微秒计算的,10的6次方分之一秒
                ff.setTimestamp(timestamp);
                frame = ff.grabImage();
                if (frame != null) {
                    if (frame.image != null) {
                        BufferedImage bufferedImage = doExecuteFrame(frame, picNum);
                        if (bufferedImage != null) {
                            files.put(picNum, bufferedImage);
                        }
                        picNum++;
                        if (count != null && picNum == count) {
                            break;
                        }
                    }
                }
                second += stepSecond;
                if(picNum > 60) {
                    break;
                }
            }
            ff.stop();
 
        } catch (Exception e) {
            log.error("下载抽帧失败,ipPort:{},videoUrl:{},msg:{}", null, videoUrl, e.getMessage());
            e.printStackTrace();
        }
 
        return files;
    }
 
    /**
     * 视频文件指定时间段的帧截取
     *
     * @param videoUrl  视频文件URL
     * @param start     视频开始的帧
     * @param count     需要截取的帧个数
     * @param isAvgTime 在截帧时 是否均匀分布计算时间
     * @return
     */
    public static Map<Integer, BufferedImage> videoIntercept(String videoUrl, int start, int count, boolean isAvgTime) {
        log.info("开始抽取视频帧数,videoUrl:{}",videoUrl);
        Frame frame = null;
        //<时间, 图片流>
        Map<Integer, BufferedImage> files = new HashMap<>();
        FFmpegFrameGrabber fFmpegFrameGrabber = new FFmpegFrameGrabber(videoUrl);
        fFmpegFrameGrabber.setOption("timeout", "40000000");
 
        try {
            fFmpegFrameGrabber.start();
            long frameTime = 1;
            if (isAvgTime) {
                frameTime = fFmpegFrameGrabber.getLengthInTime() / count / 1000000L;
                if (frameTime < 0) {
                    frameTime = 1;
                }
            }
            for (int i = start; i <= count; i++) {
                fFmpegFrameGrabber.setTimestamp(i * frameTime * 1000 * 1000);
                frame = fFmpegFrameGrabber.grabImage();
                BufferedImage bufferedImage = doExecuteFrame(frame, i);
                if (bufferedImage != null) {
                    files.put(i, bufferedImage);
                }
            }
            fFmpegFrameGrabber.stop();
        } catch (Exception E) {
            log.info("下载的视频抽帧失败,msg:" + E.getMessage());
            E.printStackTrace();
        }
        return files;
    }

    

    /*
     * @description:  BufferedImage 转 inputStream
     * @author: wangkanglu
     * @date: 2023/12/8 14:52
     * @param: [image]
     * @return: java.io.InputStream
     **/
    public static InputStream bufferedImageToInputStream(BufferedImage image) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            ImageIO.write(image, "jpg", os);
            InputStream input = new ByteArrayInputStream(os.toByteArray());
            return input;
        } catch (IOException e) {
        }
        return null;
    }
 
    public static void main(String[] args) throws IOException {
        String videoUrl = "http://vd2.bdstatic.com/mda-pej1ztfufz8axvtu/360p/h264/1684545921389774683/mda-pej1ztfufz8axvtu.mp4";
        Map<Integer, BufferedImage> integerInputStreamMap = videoUrlIntercept(videoUrl, 1, 13);
        System.out.println(integerInputStreamMap.size());
        for (Integer seconds : integerInputStreamMap.keySet()) {
            BufferedImage bufferedImage = integerInputStreamMap.get(seconds);
            String fileName = System.currentTimeMillis()+"抖音测试3" + "_" + seconds + ".jpg";
            String filePath = "D:\\Videos\\"+fileName;
            //本地磁盘存储
            // 本地图片保存地址
            ImageIO.write(bufferedImage, "png", new File(filePath));
 
            System.out.println("seconds: " + seconds + ", uploadURL: " + filePath);
        }
 
    }
}

附赠压缩图片工具类

java 复制代码
package com.wkl.testdemo.vedio;

import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.codec.binary.Base64;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.stereotype.Component;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;

@Slf4j
@Component
public class VideoFrameGrabber {

    public static void main(String[] args) throws Exception {
        // 视频地址
        String vedioUrl = "http://vd2.bdstatic.com/mda-pej1ztfufz8axvtu/360p/h264/1684545921389774683/mda-pej1ztfufz8axvtu.mp4";
        BufferedImage image = doExecuteFrameByTime(vedioUrl, 10);
        double targetSize = 10*1024;
        while (imageToBytes(image).length > targetSize) {
            float reduceMultiple = 0.5f;
            image = resizeImage(image, reduceMultiple);
        }
        // 本地图片保存地址
        ImageIO.write(image, "png", new File("D:\\Videos\\test6.jpg"));

    }



    /*
     * @description:  读取视频第n秒的图片帧
     * @author: wangkanglu
     * @date: 2023/12/8 15:05
     * @param: [videoUrl, stepSecond]
     * @return: java.awt.image.BufferedImage
     **/
    public static BufferedImage doExecuteFrameByTime(String videoUrl, Integer stepSecond) {
        FFmpegFrameGrabber ff = new FFmpegFrameGrabber(videoUrl);
        ff.setOption("timeout", "40000000");
        long timestamp =  stepSecond * 1000000L;    //视频是按照微秒计算的,10的6次方分之一秒
        try {
            ff.start();
            ff.setTimestamp(timestamp);
            Frame frame = ff.grabImage();
            if (frame == null || frame.image == null) {
                return null;
            }
            Java2DFrameConverter converter = new Java2DFrameConverter();
            BufferedImage bi = converter.getBufferedImage(frame);
            ff.stop();
            return bi;
        } catch (FrameGrabber.Exception e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 通过BufferedImage图片流调整图片大小
     * 指定压缩后长宽
     */
    public static BufferedImage resizeImage(BufferedImage originalImage, int targetWidth, int targetHeight) throws IOException {
        Image resultingImage = originalImage.getScaledInstance(targetWidth, targetHeight, Image.SCALE_AREA_AVERAGING);
        BufferedImage outputImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
        outputImage.getGraphics().drawImage(resultingImage, 0, 0, null);
        return outputImage;
    }

    /**
     * 通过BufferedImage图片流调整图片大小
     * @param originalImage
     * @param reduceMultiple 缩小倍数
     * @return
     * @throws IOException
     */
    public static BufferedImage resizeImage(BufferedImage originalImage, float reduceMultiple) throws IOException {
        int width = (int) (originalImage.getWidth() * reduceMultiple);
        int height = (int) (originalImage.getHeight() * reduceMultiple);
        Image resultingImage = originalImage.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
        BufferedImage outputImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        outputImage.getGraphics().drawImage(resultingImage, 0, 0, null);
        return outputImage;
    }

    /**
     * 压缩图片到指定大小
     * @param srcImgData
     * @param reduceMultiple 每次压缩比率
     * @return
     * @throws IOException
     */
    public static byte[] resizeImage(byte[] srcImgData, float reduceMultiple) throws IOException {
        BufferedImage bi = ImageIO.read(new ByteArrayInputStream(srcImgData));
        int width = (int) (bi.getWidth() * reduceMultiple); // 源图宽度
        int height = (int) (bi.getHeight() * reduceMultiple); // 源图高度
        Image image = bi.getScaledInstance(width, height, Image.SCALE_SMOOTH);
        BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics g = tag.getGraphics();
        g.setColor(Color.RED);
        g.drawImage(image, 0, 0, null); // 绘制处理后的图
        g.dispose();
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        ImageIO.write(tag, "JPEG", bOut);
        return bOut.toByteArray();
    }


    /**
     * BufferedImage图片流转byte[]数组
     */
    public static byte[] imageToBytes(BufferedImage bImage) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            ImageIO.write(bImage, "jpg", out);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return out.toByteArray();
    }


    /**
     * byte[]数组转BufferedImage图片流
     */
    private static BufferedImage bytesToBufferedImage(byte[] ImageByte) {
        ByteArrayInputStream in = new ByteArrayInputStream(ImageByte);
        BufferedImage image = null;
        try {
            image = ImageIO.read(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return image;
    }

}
相关推荐
丶小鱼丶1 分钟前
并发编程之【优雅地结束线程的执行】
java
市场部需要一个软件开发岗位6 分钟前
JAVA开发常见安全问题:Cookie 中明文存储用户名、密码
android·java·安全
忆~遂愿10 分钟前
GE 引擎进阶:依赖图的原子性管理与异构算子协作调度
java·开发语言·人工智能
MZ_ZXD00114 分钟前
springboot旅游信息管理系统-计算机毕业设计源码21675
java·c++·vue.js·spring boot·python·django·php
PP东17 分钟前
Flowable学习(二)——Flowable概念学习
java·后端·学习·flowable
ManThink Technology22 分钟前
如何使用EBHelper 简化EdgeBus的代码编写?
java·前端·网络
invicinble26 分钟前
springboot的核心实现机制原理
java·spring boot·后端
iWZXQxBO28 分钟前
运动控制卡 倒R角程序 G代码 halcon联合运动控制卡联合相机 运动控制卡内容
音视频
人道领域34 分钟前
SSM框架从入门到入土(AOP面向切面编程)
java·开发语言
大模型玩家七七1 小时前
梯度累积真的省显存吗?它换走的是什么成本
java·javascript·数据库·人工智能·深度学习