JavaCV實(shí)現(xiàn)讀取視頻信息及自動(dòng)截取封面圖詳解
概述
最近在對(duì)之前寫的一個(gè) Spring Boot 的視頻網(wǎng)站項(xiàng)目做功能完善,需要利用 FFmpeg 實(shí)現(xiàn)讀取視頻信息和自動(dòng)截圖的功能,查閱資料后發(fā)現(xiàn)網(wǎng)上這部分的內(nèi)容非常少,于是就有了這篇文章。
視頻網(wǎng)站項(xiàng)目地址
本文將介紹如何利用Javacv實(shí)現(xiàn)在視頻網(wǎng)站中常見的讀取視頻信息和自動(dòng)獲取封面圖的功能。
javacv 介紹
javacv可以幫助我們在java中很方便的使用 OpenCV 以及 FFmpeg 相關(guān)的功能接口
引入 javacv
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>${javacv.version}</version>
</dependency>
讀取視頻信息
創(chuàng)建 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 {
/**
* 總幀數(shù)
**/
private int lengthInFrames;
/**
* 幀率
**/
private double frameRate;
/**
* 時(shí)長
**/
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);
// 啟動(dòng) FFmpeg
grabber.start();
// 讀取視頻幀數(shù)
videoInfo.setLengthInFrames(grabber.getLengthInVideoFrames());
// 讀取視頻幀率
videoInfo.setFrameRate(grabber.getVideoFrameRate());
// 讀取視頻秒數(shù)
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 無法關(guān)閉
grabber.stop();
grabber.release();
}
} catch (FFmpegFrameGrabber.Exception e) {
log.error("getVideoInfo grabber.release failed 獲取文件信息失?。簕}", e.getMessage());
}
}
}
截圖
讀取信息沒有什么難度,但是在對(duì)視頻截圖的過程中,出現(xiàn)了一些問題,在我查找截圖實(shí)現(xiàn)的代碼時(shí),大多數(shù)的代碼都是這么寫的
/**
* 獲取視頻縮略圖
* @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);
//獲取幀數(shù)
int ffLength = ff.getLengthInFrames();
Frame f;
int i = 0;
//設(shè)置截取幀數(shù)
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;
}
這樣寫本身沒有什么問題,但是在獲取需要截取幀數(shù)的部分,使用的是通過循環(huán)來一幀一幀的判斷,這樣在視頻較短的時(shí)候沒有什么問題,但是如果視頻較長,就會(huì)出現(xiàn)嚴(yán)重的性能問題。
while (i < ffLength) {
f = ff.grabImage();
if(i == index){
......
break;
}
i++;
}
FFmpeg 的命令行參數(shù)有一個(gè) -ss 的參數(shù),使用 -ss 可以快速的幫助我們跳到視頻的指定位置,完成操作,不用一幀一幀的判斷。
所以現(xiàn)在的問題就是如何在 javacv 中實(shí)現(xiàn) -ss 參數(shù)
我在 javacv 的 GitHub Issues 中發(fā)現(xiàn)了這個(gè)操作,即使用 setTimestamp() 方法,使用 setTimestamp() 方法可以使 FFmpeg 跳轉(zhuǎn)到指定時(shí)間,完成截圖,于是,最后的截圖代碼就變成了這樣
/**
* 隨機(jī)獲取視頻截圖
* @param videFile 視頻文件
* @param count 輸出截圖數(shù)量
* @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();
// 獲取視頻總幀數(shù)
// int lengthInVideoFrames = grabber.getLengthInVideoFrames();
// 獲取視頻時(shí)長, / 1000000 將單位轉(zhuǎn)換為秒
long delayedTime = grabber.getLengthInTime() / 1000000;
Random random = new Random();
for (int i = 0; i < count; i++) {
// 跳轉(zhuǎn)到響應(yīng)時(shí)間
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, "系統(tǒng)生成截圖", 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());
}
}
}
這樣我們就能快速的實(shí)現(xiàn)截圖了。

以上就是JavaCV實(shí)現(xiàn)讀取視頻信息及自動(dòng)截取封面圖詳解的詳細(xì)內(nèi)容,更多關(guān)于JavaCV讀取視頻信息 自動(dòng)截圖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java數(shù)據(jù)結(jié)構(gòu)與算法之棧(Stack)實(shí)現(xiàn)詳解
這篇文章主要為大家詳細(xì)介紹了Java數(shù)據(jù)結(jié)構(gòu)學(xué)習(xí)筆記第二篇,Java數(shù)據(jù)結(jié)構(gòu)與算法之棧Stack實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09
Spring Cloud GateWay 路由轉(zhuǎn)發(fā)規(guī)則介紹詳解
這篇文章主要介紹了Spring Cloud GateWay 路由轉(zhuǎn)發(fā)規(guī)則介紹詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-05-05
使用工具類-java精確到小數(shù)點(diǎn)后6位
這篇文章主要介紹了使用工具類-java精確到小數(shù)點(diǎn)后6位,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
Java HashSet集合存儲(chǔ)遍歷學(xué)生對(duì)象代碼實(shí)例
這篇文章主要介紹了Java HashSet集合存儲(chǔ)遍歷學(xué)生對(duì)象代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
Redisson分布式閉鎖RCountDownLatch的使用詳細(xì)講解
分布式鎖和我們java基礎(chǔ)中學(xué)習(xí)到的synchronized略有不同,synchronized中我們的鎖是個(gè)對(duì)象,當(dāng)前系統(tǒng)部署在不同的服務(wù)實(shí)例上,單純使用synchronized或者lock已經(jīng)無法滿足對(duì)庫存一致性的判斷。本次主要講解基于rediss實(shí)現(xiàn)的分布式鎖2023-02-02
學(xué)習(xí)Java設(shè)計(jì)模式之觀察者模式
這篇文章主要為大家介紹了Java設(shè)計(jì)模式中的觀察者模式,對(duì)Java設(shè)計(jì)模式感興趣的小伙伴們可以參考一下2016-01-01
解決 IDEA 創(chuàng)建 Gradle 項(xiàng)目沒有src目錄問題
這篇文章主要介紹了解決 IDEA 創(chuàng)建 Gradle 項(xiàng)目沒有src目錄問題,本文圖文并茂給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-06-06
阿里nacos+springboot+dubbo2.7.3統(tǒng)一處理異常的兩種方式
本文主要介紹了阿里nacos+springboot+dubbo2.7.3統(tǒng)一處理異常的兩種方式,文中根據(jù)實(shí)例編碼詳細(xì)介紹的十分詳盡,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03

