使用JavaCV实现读取视频信息及自动截取封面图

概述

最近在对之前写的一个 Spring Boot 的视频网站项目做功能完善,需要利用 FFmpeg 实现读取视频信息和自动截图的功能,查阅资料后发现网上这部分的内容非常少,于是就有了这篇文章。

视频网站项目地址 GitHub:https://github.com/PuZhiweizuishuai/PornTube
码云: https://gitee.com/puzhiweizuishuai/VideoWeb

本文将介绍如何利用Javacv实现在视频网站中常见的读取视频信息和自动获取封面图的功能。

javacv 介绍

javacv可以帮助我们在java中很方便的使用 OpenCV 以及 FFmpeg 相关的功能接口
项目地址:https://github.com/bytedeco/javacv

引入 javacv

        <dependency>             <groupId>org.bytedeco</groupId>             <artifactId>javacv-platform</artifactId>             <version>${javacv.version}</version>         </dependency> 

读取视频信息

创建 VideoInfo 类

package com.buguagaoshu.porntube.vo;  import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.Setter;  /**  * @author Pu Zhiwei {@literal puzhiweipuzhiwei@foxmail.com}  * create          2022-06-06 19:15  */ @Getter @Setter public class VideoInfo {     /**      * 总帧数      **/     private int lengthInFrames;      /**      * 帧率      **/     private double frameRate;      /**      * 时长      **/     private double duration;      /**      * 视频编码      */     private String videoCode;     /**      * 音频编码      */     private String audioCode;      private int width;     private int height;     private int audioChannel;     private String md5;     /**      * 音频采样率      */     private Integer sampleRate;      public String toJson() {         try {             ObjectMapper objectMapper = new ObjectMapper();             return objectMapper.writeValueAsString(this);         } catch (Exception e) {             return "";         }     } } 

使用 FFmpegFrameGrabber 读取视频信息

 public static VideoInfo getVideoInfo(File file) {         VideoInfo videoInfo = new VideoInfo();         FFmpegFrameGrabber grabber = null;         try {             grabber = new FFmpegFrameGrabber(file);             // 启动 FFmpeg             grabber.start();              // 读取视频帧数             videoInfo.setLengthInFrames(grabber.getLengthInVideoFrames());  			// 读取视频帧率             videoInfo.setFrameRate(grabber.getVideoFrameRate());              // 读取视频秒数             videoInfo.setDuration(grabber.getLengthInTime() / 1000000.00);                          // 读取视频宽度             videoInfo.setWidth(grabber.getImageWidth());              // 读取视频高度             videoInfo.setHeight(grabber.getImageHeight());                           videoInfo.setAudioChannel(grabber.getAudioChannels());              videoInfo.setVideoCode(grabber.getVideoCodecName());              videoInfo.setAudioCode(grabber.getAudioCodecName());             // String md5 = MD5Util.getMD5ByInputStream(new FileInputStream(file));              videoInfo.setSampleRate(grabber.getSampleRate());             return videoInfo;         } catch (Exception e) {             e.printStackTrace();             return null;         } finally {             try {                 if (grabber != null) {                     // 此处代码非常重要,如果没有,可能造成 FFmpeg 无法关闭                     grabber.stop();                     grabber.release();                 }             } catch (FFmpegFrameGrabber.Exception e) {                 log.error("getVideoInfo grabber.release failed 获取文件信息失败:{}", e.getMessage());             }         }     } 

截图

读取信息没有什么难度,但是在对视频截图的过程中,出现了一些问题,在我查找截图实现的代码时,大多数的代码都是这么写的

    /**      * 获取视频缩略图      * @param filePath:视频路径      * @param mod:视频长度/mod获取第几帧      * @throws Exception      */     public static String randomGrabberFFmpegImage(String filePath, int mod) {         String targetFilePath = "";         try{             FFmpegFrameGrabber ff = FFmpegFrameGrabber.createDefault(filePath);             ff.start();             //图片位置是否正确             String rotate = ff.getVideoMetadata(ROTATE);             //获取帧数             int ffLength = ff.getLengthInFrames();             Frame f;             int i = 0;             //设置截取帧数             int index = ffLength / mod;             while (i < ffLength) {                 f = ff.grabImage();                 if(i == index){                     if (null != rotate && rotate.length() > 1) {                         OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();                         IplImage src = converter.convert(f);                         f = converter.convert(rotate(src, Integer.parseInt(rotate)));                     }                     targetFilePath = getImagePath(filePath, i);                     doExecuteFrame(f, targetFilePath);                     break;                 }                 i++;             }             ff.stop();         }catch (Exception e){             log.error("获取视频缩略图异常:" + e.getMessage());         }         return targetFilePath;     }  

这样写本身没有什么问题,但是在获取需要截取帧数的部分,使用的是通过循环来一帧一帧的判断,这样在视频较短的时候没有什么问题,但是如果视频较长,就会出现严重的性能问题。

  while (i < ffLength) {                 f = ff.grabImage();                 if(i == index){ 					......                     break;                 }                 i++;             } 

FFmpeg 的命令行参数有一个 -ss 的参数,使用 -ss 可以快速的帮助我们跳到视频的指定位置,完成操作,不用一帧一帧的判断。

所以现在的问题就是如何在 javacv 中实现 -ss 参数

我在 javacv 的 GitHub Issues 中发现了这个操作,即使用 setTimestamp() 方法,使用 setTimestamp() 方法可以使 FFmpeg 跳转到指定时间,完成截图,于是,最后的截图代码就变成了这样

  /**      * 随机获取视频截图      * @param videFile 视频文件      * @param count 输出截图数量      * @return 截图列表      * */     public static List<FileTableEntity> randomGrabberFFmpegImage(File videFile, int count, long userId) {         FFmpegFrameGrabber grabber = null;          String path = FileTypeEnum.filePath();         try {             List<FileTableEntity> images = new ArrayList<>(count);             grabber = new FFmpegFrameGrabber(videFile);             grabber.start();             // 获取视频总帧数             // int lengthInVideoFrames = grabber.getLengthInVideoFrames();             // 获取视频时长, / 1000000 将单位转换为秒             long delayedTime = grabber.getLengthInTime() / 1000000;              Random random = new Random();             for (int i = 0; i < count; i++) {                 // 跳转到响应时间                 grabber.setTimestamp((random.nextInt((int)delayedTime - 1) + 1) * 1000000L);                 Frame f = grabber.grabImage();                 Java2DFrameConverter converter = new Java2DFrameConverter();                 BufferedImage bi = converter.getBufferedImage(f);                 String imageName = FileTypeEnum.newFilename(SUFFIX);                 File out = Paths.get(path, imageName).toFile();                 ImageIO.write(bi, "jpg", out);                 FileTableEntity fileTable = FileUtils.createFileTableEntity(imageName, SUFFIX, path, f.image.length, "系统生成截图", userId, FileTypeEnum.VIDEO_PHOTO.getCode());                 images.add(fileTable);             }             return images;         } catch (Exception e) {             return null;         } finally {             try {                 if (grabber != null) {                     grabber.stop();                     grabber.release();                 }             } catch (FFmpegFrameGrabber.Exception e) {                 log.error("getVideoInfo grabber.release failed 获取文件信息失败:{}", e.getMessage());             }         }     } 

这样我们就能快速的实现截图了。

使用JavaCV实现读取视频信息及自动截取封面图

版权

本文首发于 https://www.buguagaoshu.com/archives/shi-yong-javacv-shi-xian-du-qu-shi-pin-xin-xi-ji-zi-dong-jie-qu-feng-mian-tu

转载请注明来源

发表评论

相关文章