Java获取视频缩略图(关键帧)。两种方法总结,汇总,踩坑。

最近因公司业务要做视频视频缩略图功能

首先说一下服务器环境,减少弯路。

服务器是ubuntu下打的docker镜像。

ok,继续。

总结两种方法:

第一种:通过Javacv生成。

第二种:服务器安装ffmpeg生成。

一:通过Java代码生成。

首先引入jar包:


    org.bytedeco
    javacpp
    1.4.3


	org.bytedeco
	javacv
	1.4.3


	org.bytedeco.javacpp-presets
	ffmpeg-platform
	4.0.2-1.4.3

实现代码:

/**
* @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;
    }

注意:

  1. 服务器的docker容器要安装ffmpeg。
  2. 路径要挂载映射到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 项目名

总结: 

  1. 使用javacv生成速度比较慢,但是操作会简单一点。
  2. 使用服务器ffmpeg速度会快很多,操作麻烦。我选择的是第二种方法。

小白总结,如有不足,请多指教。

你可能感兴趣的:(Java学习之路,java,docker,视频处理)