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

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

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

服务器是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;
    }

注意:

  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速度会快很多,操作麻烦。我选择的是第二种方法。

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

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Flutter 提供了一个 `VideoThumbnail` 类,可以用于获取视频缩略图。以下是一个使用示例: ```dart import 'dart:io'; import 'package:flutter/material.dart'; import 'package:video_thumbnail/video_thumbnail.dart'; class VideoThumbnailScreen extends StatefulWidget { final String videoPath; const VideoThumbnailScreen({Key key, this.videoPath}) : super(key: key); @override _VideoThumbnailScreenState createState() => _VideoThumbnailScreenState(); } class _VideoThumbnailScreenState extends State<VideoThumbnailScreen> { String _thumbnailPath; @override void initState() { super.initState(); _generateThumbnail(); } Future<void> _generateThumbnail() async { final thumbnailPath = await VideoThumbnail.thumbnailFile( video: widget.videoPath, thumbnailPath: (await getTemporaryDirectory()).path, imageFormat: ImageFormat.PNG, ); setState(() { _thumbnailPath = thumbnailPath; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Video Thumbnail'), ), body: Center( child: _thumbnailPath != null ? Image.file(File(_thumbnailPath)) : CircularProgressIndicator(), ), ); } } ``` 在上述示例中,`VideoThumbnailScreen` 是一个 `StatefulWidget`,接收一个 `videoPath` 参数,该参数为视频文件的路径。在 `initState` 方法中,调用 `_generateThumbnail` 方法来生成视频缩略图。`_generateThumbnail` 方法使用 `VideoThumbnail.thumbnailFile` 来生成缩略图,并将结果保存到临时目录下的文件中。最后,在 `build` 方法中显示生成的缩略图,或者显示一个加载指示器。 注意,为了使用 `VideoThumbnail` 类,你需要在 `pubspec.yaml` 文件中添加 `video_thumbnail` 依赖。请确保在使用前进行相应的导入和安装依赖。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值