最近因公司业务要做视频视频缩略图功能首先说一下服务器环境,减少弯路。服务器是ubuntu下打的docker镜像。ok,继续。
总结两种方法:
第一种:通过Javacv生成。
第二种:服务器安装ffmpeg生成。
一:通过Java代码生成。
首先引入jar包:
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>1.4.3</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.4.3</version>
</dependency>
<dependency>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>ffmpeg-platform</artifactId>
<version>4.0.2-1.4.3</version>
</dependency>
实现代码:
/**
* @description //生成视频缩略图的URL地址
* @param videoFilePath 视频在minIO路径
* @return String 缩略图URL
*/
public String dealVideo(String videoFilePath) throws Exception{
//我的视频是从minIO文件服务器下载的
InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
.bucket(minIOProperties.getBucketName())
.object(videoFilePath)
.build());
//这里用的打包后target/classes/下新建的tmp路径。
//建议写死绝对路径,否则在服务器上会找不到路径
String path = "../target/classes/tmp/";
//Opslab.SLASH= "/"
String fileName = StringUtils.join(videoFilePath.substring(videoFilePath.lastIndexOf(Opslab.SLASH)+1), "_thumb.jpg");
String filePath = StringUtils.join(path, fileName);
File targetFile = new File(filePath);
try {
FFmpegFrameGrabber ff = new FFmpegFrameGrabber(inputStream);
ff.start();
// 视频总帧数
int videoLength = ff.getLengthInFrames();
Frame f = null;
int i = 0;
while (i < videoLength) {
// 过滤前20帧,因为前20帧可能是全黑的
// 这里看需求,也可以直接根据帧数取图片
f = ff.grabFrame();
if (i > 20 && f.image != null) {
break;
}
i++;
}
int owidth = f.imageWidth;
int oheight = f.imageHeight;
// 对截取的帧进行等比例缩放
int width = 800;
int height = (int) (((double) width / owidth) * oheight);
Java2DFrameConverter converter = new Java2DFrameConverter();
BufferedImage fecthedImage = converter.getBufferedImage(f);
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
bi.getGraphics().drawImage(fecthedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH),
0, 0, null);
ImageIO.write(bi, "jpg", targetFile);
ff.stop();
//我这边是是要生成链接返回给前端的。
//所以我做了文件上传功能返回了文件的url
FileInputStream fileInput = new FileInputStream(targetFile);
MultipartFile toMultipartFile = new MockMultipartFile("file",targetFile.getName(),"application/json;charset=UTF-8", IOUtils.toByteArray(fileInput));
toMultipartFile.getInputStream();
String minIoUrl = videoFilePath.substring(0, videoFilePath.lastIndexOf(Opslab.SLASH) + 1);
uploadMultipartFile是我自己封装的minIO上传方法
uploadMultipartFile(toMultipartFile, minIoUrl);
fileInput.close();
targetFile.delete();
//presignedGetObject是我自己封装的minIO生成链接方法
return presignedGetObject(StringUtils.join(minIoUrl,fileName),60*60*24);
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
第二种:通过服务器安装ffmpeg生成。
显示代码如下:
/**
* @description //通过Linux命令生成缩略图
* @param videoFilePath 视频在minIO路径
* @return String 缩略图URL
*/
public String executeLinuxCmd(String videoFilePath) throws Exception {
//保留原始路径
String videoFilePath1 = videoFilePath;
//获取视频流
InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
.bucket(minIOProperties.getBucketName())
.object(videoFilePath)
.build());
//去除路径中的空格,因为linux命令空格会报错
videoFilePath = videoFilePath.replace(" ","");
//生成暂存文件的地址,一定要是绝对路径,我前面省略了自己项目的地址
String path = "../target/classes/tmp/";
//视频名
String videoName = StringUtils.join(videoFilePath.substring(videoFilePath.lastIndexOf(Opslab.SLASH) + 1));
//视频存放在本地的绝对路径
String videoPath = StringUtils.join(path, videoName);
//缩略图名
String thumbName = StringUtils.join(videoName,"_thumb.jpg");
//缩略图绝对路径
String thumbPath = StringUtils.join(path, thumbName);
try {
//把视频流转存为本地文件
FileUtils.copyInputStreamToFile(inputStream, new File(videoPath));
} catch (IOException e) {
e.printStackTrace();
throw new BaseException(ResultStatusEnums.PARAMETER_CODE);
}
//linux命令
String cmd = StringUtils.join("ffmpeg -ss 00:00:30 -i ",videoPath," -frames:v 1 ",thumbPath);
log.info("执行命令[ " + cmd + "]");
Runtime run = Runtime.getRuntime();
try {
Process process = run.exec(cmd);
String line;
BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuffer out = new StringBuffer();
while ((line = stdoutReader.readLine()) != null) {
out.append(line);
}
try {
process.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
process.destroy();
log.info("命令执行结束=======文件上传开始");
//获取本地视频,缩略图文件
File videoFile = new File(videoPath);
File thumbFile = new File(thumbPath);
//上传缩略图
FileInputStream thumbInput = new FileInputStream(thumbFile);
MultipartFile toMultipartFile = new MockMultipartFile("file",thumbFile.getName(),"application/json;charset=UTF-8", IOUtils.toByteArray(thumbInput));
toMultipartFile.getInputStream();
String minIoUrl = videoFilePath1.substring(0, videoFilePath1.lastIndexOf(Opslab.SLASH) + 1);
uploadMultipartFile(toMultipartFile, minIoUrl);
//删除本地文件
videoFile.delete();
thumbFile.delete();
//返回minIO文件URL
return presignedGetObject(StringUtils.join(minIoUrl,thumbName),60*60*24);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
注意:
- 服务器的docker容器要安装ffmpeg。
- 路径要挂载映射到docker容器。
dockerfile文件:
#FROM alpine
FROM openjdk:8-jre-alpine
#docker安装ffmpeg。
RUN apk add ffmpeg
ENV APP_NAME
ENV APP_VERSION
ADD $APP_NAME-$APP_VERSION.jar app.jar
EXPOSE 8080
RUN echo "Asia/Shanghai" > /etc/timezone
ENTRYPOINT ["java","-jar","/app.jar", "--server.port=8080"]
CMD ["java", "-version"]
docker映射路径文件夹:
docker run -v 本地绝对路径:docker容器路径(会自动生成) -d -p 8088:8080 --name 项目名
总结:
- 使用javacv生成速度比较慢,但是操作会简单一点。
- 使用服务器ffmpeg速度会快很多,操作麻烦。我选择的是第二种方法。