Java提取视频中的指定桢数生成视频缩略图的解决方案
Java提取视频中的指定桢数生成视频缩略图的解决方案
1.1 开发背景
在Java后端开发中,当返回给前端或移动端视频列表的时候,客户端往往并不直接加载所有视频,而是先加载后端返回的视频缩略图列表。
然而这个视频缩略图列表从哪里来呢?总不能让上传视频的用户自己上传吧?
答案当然是不能, 我们Java后端其实是有办法解决这个问题的。
一种方法是使用FFmpeg 视频定时截图工具,这种局限在必须部署在windows 服务器。
另外一种是通过程序Java 代码编程方式使用JavaCV 类库实现。
那么什么是JavaCV 呢?
JavaCV
是对各种常用计算机视觉库的封装后的一组jar包,其中封装了FFmpeg
、OpenCV
等计算机视觉编程人员常用库的接口,可以通过其中的Utility类方便的在包括Android在内的Java平台上调用这些接口。- 最开始
Javacv
是googlecode
下面的一个项目,后来迁移到了github,因此JavaCV
相关的包名也由com.googlecode.javacv
改为org.bytedeco.javacv
。目前最新版本是 1.3.
上面这段话也解开了我的一个疑惑。
因为我之前尝试maven 中心仓库搜索javacv 后发现有这三个类库,到底使用哪一种开始心存疑惑。
google java cv 那个版本太老了,也就是说我们如果使用javacv,直接使用java CV Platform 类库即可。至于为什么不用JavaCV 那个,其实我也找了一篇博文,但是试了下貌似需要缺少so 库,于是放弃了。
JavaCV目前最新版本是 1.5.3。项目地址:https://github.com/bytedeco/javacv
1.2 如何使用JavaCV?
1.2.1 引入依赖
<!-- https://mvnrepository.com/artifact/org.bytedeco/javacv-platform -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.3</version>
</dependency>
1.2.2 编写工具类库
编写工具类库SmartFileUtils.java
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.File;
import java.io.IOException;
import java.util.UUID;
/**
* @author qing-feng.zhao
*/
public class SmartFileUtils {
/**
* * 截取视频第六帧的图片
*
* @param filePath 视频路径
* @param dir 文件存放的根目录
* @return 图片的相对路径 例:pic/1.png
*/
public static String videoImage(String filePath, String dir) throws FrameGrabber.Exception {
String pngPath = "";
FFmpegFrameGrabber ff = FFmpegFrameGrabber.createDefault(filePath);
ff.setFormat("mp4");
ff.start();
int ffLength = ff.getLengthInFrames();
Frame f;
int i = 0;
while (i < ffLength) {
f = ff.grabImage();
//截取第6帧
if ((i > 5) && (f.image != null)) {
//生成图片的相对路径 例如:pic/uuid.png
pngPath = getPngPath();
//执行截图并放入指定位置
doExecuteFrame(f, dir + pngPath);
break;
}
i++;
}
ff.stop();
return pngPath;
}
/**
* 生成图片的相对路径
*
* @return 图片的相对路径 例:pic/1.png
*/
private static String getPngPath() {
return "pic/" + getUUID() + ".png";
}
/**
* 生成唯一的uuid
*
* @return uuid
*/
private static String getUUID() {
return UUID.randomUUID().toString().replace("-", "");
}
/**
* 截取缩略图
*
* @param f Frame
* @param targerFilePath:封面图片存放路径
*/
private static void doExecuteFrame(Frame f, String targerFilePath) {
String imagemat = "png";
if (null == f || null == f.image) {
return;
}
Java2DFrameConverter converter = new Java2DFrameConverter();
BufferedImage bi = converter.getBufferedImage(f);
File output = new File(targerFilePath);
try {
ImageIO.write(bi, imagemat, output);
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.2.3 测试调用
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FrameGrabber;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@Slf4j
class SmartFileUtilsTest {
private String testPath;
@BeforeEach
void setUp() {
testPath="/Users/zhaoqingfeng/Documents/temp/test.mp4";
//testPath="https://v.qq.com/txp/iframe/player.html?vid=q0024wtk0v8";
}
@AfterEach
void tearDown() {
}
@Test
void videoImage() {
try {
String filePath=SmartFileUtils.videoImage(testPath,"/Users/zhaoqingfeng/Documents/temp/");
log.info("{}",filePath);
} catch (FrameGrabber.Exception e) {
log.error("出错",e);
}
}
}
然后就可以看到/Users/zhaoqingfeng/Documents/temp/test.mp4
视频的缩略图已经存放到/Users/zhaoqingfeng/Documents/temp/pic/
文件夹下了,图片名称命名我们使用了UUID.
PS:
如果出现错误,javacv-error-has-setformat-been-called 那么可能视频地址文件不对,请使用绝对路径。
另外,代码里不要少了这一行:ff.setFormat("mp4");
测试二在尝试获取腾讯视频地址的时候由于无法获取到视频真实文件地址,导致报错失败。
本篇完~