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

作者:陕西颜值扛把子 时间:2022-05-20 14:43:05 

概述

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

视频网站项目地址

GitHub

码云

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

javacv 介绍

javacv可以帮助我们在java中很方便的使用 OpenCV 以及 FFmpeg 相关的功能接口

项目地址

引入 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.cnblogs.com/puzhiwei/p/16353037.html

标签:JavaCV,视频,信息,自动截图
0
投稿

猜你喜欢

  • C语言文件操作之fread函数详解

    2023-07-06 18:24:15
  • Java客户端调用.NET的WebService实例

    2023-11-03 17:22:00
  • sqlite数据库的介绍与java操作sqlite的实例讲解

    2023-05-09 03:07:40
  • 浅谈java项目与javaweb项目导入jar包的区别

    2023-11-11 11:06:19
  • MyBatis-Plus找不到Mapper.xml文件的几种解决方法

    2023-11-24 03:37:52
  • Android如何让WebView中的HTML5页面实现视频全屏播放

    2023-07-29 00:32:06
  • 详解Kotlin:forEach也能break和continue

    2022-05-03 01:24:10
  • android中使用SharedPreferences进行数据存储的操作方法

    2023-06-16 17:37:42
  • netty pipeline中的inbound和outbound事件传播分析

    2023-08-27 06:57:00
  • Java编程实现非对称加密的方法详解

    2023-08-24 01:21:26
  • java泛型中占位符T和?有什么区别

    2022-07-05 02:53:56
  • java后台接收app上传的图片的示例代码

    2022-11-03 00:04:15
  • Java裁剪压缩PNG图片,透明背景色变黑的解决方案

    2023-11-25 13:21:27
  • Java面试题冲刺第二十三天--分布式

    2023-09-24 07:30:43
  • 关于java中构造函数的一些知识详解

    2023-11-27 22:09:11
  • 解决Java & Idea启动tomcat的中文乱码问题

    2022-05-31 05:48:35
  • springboot 使用自定义的aspect的示例代码

    2023-08-06 08:55:14
  • 使用IDEA搭建SSM框架的详细教程(spring + springMVC +MyBatis)

    2023-04-24 07:35:45
  • 解决springboot启动失败的问题('hibernate.dialect' not set)

    2023-11-09 03:21:21
  • SpringCloud用Zookeeper搭建配置中心的方法

    2022-12-17 18:16:18
  • asp之家 软件编程 m.aspxhome.com