Java实现截取视频第一帧

2023-09-19 10:24:41

目录

前言

一、通过Java借助第三方库实现

1.引用ffmpeg

使用maven,导入pom依赖:

 工具类

2.引用jcodec

二、使用第三方存储自带的方法实现(如阿里云OSS、华为云OBS)


前言

在实际项目中,会遇到上传视频后,需要截取视频的首帧或指定帧为图片,作为展示使用的需求。这个需求本身并不难,而且网上一搜一大把,今天就针对网上的部分方法做个总结。


一、通过Java借助第三方库实现

1.引用ffmpeg

  • 使用maven,导入pom依赖:

        <!-- 操作视频流 -->
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacpp</artifactId>
            <version>1.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv</artifactId>
            <version>1.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco.javacpp-presets</groupId>
            <artifactId>opencv-platform</artifactId>
            <version>3.4.1-1.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco.javacpp-presets</groupId>
            <artifactId>ffmpeg-platform</artifactId>
            <version>3.4.2-1.4.1</version>
        </dependency>


  •  工具类

import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.javacpp.opencv_core.IplImage;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber.Exception;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.bytedeco.javacv.OpenCVFrameConverter;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import lombok.extern.slf4j.Slf4j;

/**
 * 视频操作工具类
 */
@Slf4j
public class VideoUtils {

    /**
     * 截取视频第一帧为图片展示
     *
     * @param filePath       视频路径
     * @param targetFilePath 第一帧图片存储位置
     * @param targetFileName 图片名称
     */
    public static void getVideoFirstFrameImage(String filePath, String targetFilePath, String targetFileName) throws Exception {
        FFmpegFrameGrabber ff = FFmpegFrameGrabber.createDefault(filePath);
        ff.start();
        String rotate = ff.getVideoMetadata("rotate");
        Frame f;
        int i = 0;
        while (i < 1) {
            f = ff.grabImage();
            IplImage src;
            if (null != rotate && rotate.length() > 1) {
                OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
                src = converter.convert(f);
                f = converter.convert(rotate(src, Integer.parseInt(rotate)));
            }
            doExecuteFrame(f, targetFilePath, targetFileName);
            i++;
        }
        ff.stop();
    }

    /**
     * 进行旋转角度操作(为了保证截取到的第一帧图片与视频中的角度方向保持一致)
     */
    public static IplImage rotate(IplImage src, int angle) {
        IplImage img = IplImage.create(src.height(), src.width(), src.depth(), src.nChannels());
        opencv_core.cvTranspose(src, img);
        opencv_core.cvFlip(img, img, angle);
        return img;
    }

    public static void doExecuteFrame(Frame f, String targetFilePath, String targetFileName) {
        if (null == f || null == f.image) {
            return;
        }
        Java2DFrameConverter converter = new Java2DFrameConverter();
        String imageMat = "jpg";
        String fileName = targetFilePath + File.separator + targetFileName + "." + imageMat;
        BufferedImage bi = converter.getBufferedImage(f);
        File output = new File(fileName);
        try {
            ImageIO.write(bi, imageMat, output);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        String url = "C:\\Users\\admin\\Desktop\\test.mp4";
        getVideoFirstFrameImage(url, "C:\\Users\\admin\\Desktop", "first");
    }

}


2.引用jcodec

        网上视频截取第一帧的案例还是比较多的,普遍的方法主要是使用ffmpeg对其截取。在实践过程中,发现在自己本地或在window上截取都是成功的。但在Linux 环境中截取失败,这里将失败内容贴出来,供他人查看失败原因

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x000000000000d1f6, pid=14, tid=0x00007fe8b67e6b10
#
# JRE version: OpenJDK Runtime Environment (8.0_212-b04) (build 1.8.0_212-b04)
# Java VM: OpenJDK 64-Bit Server VM (25.212-b04 mixed mode linux-amd64 compressed oops)
# Derivative: IcedTea 3.12.0
# Distribution: Custom build (Sat May  4 17:33:35 UTC 2019)
# Problematic frame:
# C  0x000000000000d1f6
#
# Core dump written. Default location: //core or core.14
#
# If you would like to submit a bug report, please include
# instructions on how to reproduce the bug and visit:
#   https://icedtea.classpath.org/bugzilla
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#

---------------  T H R E A D  ---------------

Current thread (0x00005565ead9c800):  JavaThread "http-nio-8889-exec-9" daemon [_thread_in_native, id=109, stack(0x00007fe8b66e6000,0x00007fe8b67e6ad0)]

siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr: 0x000000000000d1f6

(省略部分)

Stack: [0x00007fe8b66e6000,0x00007fe8b67e6ad0],  sp=0x00007fe8b67e1558,  free space=1005k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
C  0x000000000000d1f6

Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  org.bytedeco.javacpp.avformat.avformat_open_input(Lorg/bytedeco/javacpp/avformat$AVFormatContext;Ljava/lang/String;Lorg/bytedeco/javacpp/avformat$AVInputFormat;Lorg/bytedeco/javacpp/avutil$AVDictionary;)I+0
j  org.bytedeco.javacv.FFmpegFrameGrabber.startUnsafe()V+624
j  org.bytedeco.javacv.FFmpegFrameGrabber.start()V+1
j  com.megvii.qingqiu.middle.platform.provider.util.VideoFrameUtil.fetchFrame(Ljava/io/File;Ljava/io/File;)V+26
j  com.megvii.qingqiu.middle.platform.provider.util.VideoFrameUtil.fetchFrame(Lorg/springframework/web/multipart/MultipartFile;)Ljava/io/File;+148
j  

(省略部分)

 现在将 ffmpeg 截取的方式改为 jcodecjcodec-javase 来处理。

  • 导入pom依赖:
  <dependency>
            <groupId>org.jcodec</groupId>
            <artifactId>jcodec</artifactId>
            <version>0.2.5</version>
        </dependency>
        <dependency>
            <groupId>org.jcodec</groupId>
            <artifactId>jcodec-javase</artifactId>
            <version>0.2.5</version>
        </dependency>

        <!--<dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv</artifactId>
            <version>1.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacpp</artifactId>
            <version>1.4.1</version>
        </dependency>

        <dependency>
            <groupId>org.bytedeco.javacpp-presets</groupId>
            <artifactId>opencv-platform</artifactId>
            <version>3.4.1-1.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco.javacpp-presets</groupId>
            <artifactId>ffmpeg-platform</artifactId>
            <version>3.4.2-1.4.1</version>
        </dependency>-->
  •  工具类
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.jcodec.api.FrameGrab;
import org.jcodec.api.JCodecException;
import org.jcodec.common.model.Picture;
import org.jcodec.scale.AWTUtil;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.UUID;

/**
 * 视频操作工具类
 */
@Slf4j
public class VideoUtils {

    /*** 图片格式*/
    private static final String FILE_EXT = "jpg";

    /*** 帧数*/
    private static final int THUMB_FRAME = 5;

    /**
     * 获取指定视频的帧并保存为图片至指定目录
     *
     * @param videoFilePath 源视频文件路径
     * @param frameFilePath 截取帧的图片存放路径
     */
    public static void fetchFrame(String videoFilePath, String frameFilePath) throws Exception {
        File videoFile = new File(videoFilePath);
        File frameFile = new File(frameFilePath);
        getThumbnail(videoFile, frameFile);
    }

    /**
     * 获取指定视频的帧并保存为图片至指定目录
     *
     * @param videoFile  源视频文件
     * @param targetFile 截取帧的图片
     */
    public static void fetchFrame(MultipartFile videoFile, File targetFile) throws Exception {
        File file = new File(videoFile.getName());
        FileUtils.copyInputStreamToFile(videoFile.getInputStream(), file);
        getThumbnail(file, targetFile);
    }

    /**
     * 获取指定视频的帧并保存为图片至指定目录
     *
     * @param videoFile 源视频文件
     */
    public static File fetchFrame(MultipartFile videoFile) {
        String originalFilename = videoFile.getOriginalFilename();
        File file = new File(originalFilename);
        File targetFile = null;
        try {
            FileUtils.copyInputStreamToFile(videoFile.getInputStream(), file);

            int i = originalFilename.lastIndexOf(".");
            String imageName;

            if (i > 0) {
                imageName = originalFilename.substring(0, i);
            } else {
                imageName = UUID.randomUUID().toString().replace("-", "");
            }
            imageName = imageName + ".jpg";
            targetFile = new File(imageName);
            getThumbnail(file, targetFile);
        } catch (Exception e) {
            log.error("获取视频指定帧异常:", e);
        } finally {
            if (file.exists()) {
                file.delete();
            }
        }
        log.debug("视频文件 - 帧截取 - 处理结束");
        return targetFile;
    }

    /**
     * 获取第一帧缩略图
     *
     * @param videoFile  视频路径
     * @param targetFile 缩略图目标路径
     */
    public static void getThumbnail(File videoFile, File targetFile) {
        try {
            // 根据扩展名创建一个新文件路径
            Picture picture = FrameGrab.getFrameFromFile(videoFile, THUMB_FRAME);
            BufferedImage bufferedImage = AWTUtil.toBufferedImage(picture);
            ImageIO.write(bufferedImage, FILE_EXT, targetFile);
        } catch (IOException | JCodecException e) {
            e.printStackTrace();
            log.error("获取第一帧缩略图异常:", e);
        }
    }

    public static void main(String[] args) {
        try {
            long startTime = System.currentTimeMillis();
            getThumbnail(new File("C:\\Users\\admin\\Desktop\\test.mp4"), new File("C:\\Users\\admin\\Desktop\\test.jpg"));
            System.out.println("截取图片耗时:" + (System.currentTimeMillis() - startTime));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

二、使用第三方存储自带的方法实现(如阿里云OSS、华为云OBS)

如果我们的视频是上传并保存在第三方服务器上的,那么,我们可以使用阿里提供的视频截帧方法,根据url直接截取视频的指定帧。
具体方法就是在视频的 url 后面,加上一些参数,然后访问这个新的url即可得到指定的截图。
以下是以阿里云OSS为例:

要加入的参数:
"?x-oss-process=video/snapshot,t_0,f_jpg,w_0,h_0,m_fast,ar_auto"

新的url:
"https://gnd.oss-cn-zhangjiakou.aliyuncs.com/vidio/test.map?x-oss-process=video/snapshot,t_0,f_jpg,w_0,h_0,m_fast,ar_auto" (直接访问这个新的url即可得到截图)

参数说明 

注意事项

  • 仅支持对视频编码格式为H264和H265的视频文件进行视频截帧。
  • OSS默认不保存视频截帧的图片,视频截帧的图片需手动下载并保存至本地。

亲测了一下,主流的视频格式,如:mp4、mov、avi等均可以采用这种方式进行截帧。

具体操作请参见阿里官方文档:阿里云视频截帧操作文档

其它的第三方的云存储跟阿里云的用法,大同小异,根据各自的说明文档,进行开发就行。

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、评论、收藏➕关注,您的支持是我坚持写作最大的动力。 

更多推荐

蒙特卡洛树搜索(MCTS)在Python中实现井字游戏策略优化详细教程

1.介绍井字游戏(TicTacToe)是大家都很熟悉的一款策略游戏,两个玩家轮流在3x3的棋盘上放置自己的标记(通常是’X’和’O’),目标是在任意方向上(横、竖、斜)连续三个自己的标记。而蒙特卡洛树搜索(MCTS)则是一种广泛用于复杂策略游戏(例如围棋、象棋等)的算法。在本文中,我们将结合这两者,使用MCTS为井字游

iOS系统下轻松构建自动化数据收集流程

目录python的优势ShortcutsApp介绍如何结合Python与ShortcutAppiOS系统下轻松构建自动化数据收集流程总结在当今的数字化时代,数据已经成为企业成功的关键因素之一。然而,随着业务的发展和数据量的增加,手动收集和分析数据的方式已经不再可行。在iOS系统下,我们可以利用一些工具和技术来轻松构建自

Guava精讲(三)-Caches,同步DB数据到缓存

在开发中,我们经常需要从数据库中读取数据并进行频繁的读取操作。缓存在各种场景中都有运用,例如,当一个值的计算或检索成本很高,而且在某个输入中需要多次使用该值时,就应该考虑使用缓存,因此将数据缓存在内存中可以显著提高应用程序的性能。问题描述假设我们正在开发一个电子商务网站,需要频繁地显示商品信息。商品信息存储在数据库中,

SpringMVC之JSON返回&异常处理机制

json处理统一异常处理1.json处理//pom.xml<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-in

服务器搭建(TCP套接字)-select版(服务端)

一、select头文件#include<sys/select.h>二、select原型intselect(intnfds,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,structtimeval*timeout);select()是一个系统调用函数,用于在多个文件描述符

K8S架构原理

目录一、k8s概述1、什么是k8s?2、特性3、主要功能二、集群架构与组件1.Master组件(1)Kube-apiserver(2)Kube-controller-manager(3)Kube-scheduler调度算法:2.配置存储中心3.Node组件(1)Kubelet(2)Kube-Proxy(3)docker

【SpringMVC】之自定义注解

文章目录一、Java注解1.1简介1.2分类1.2.1JDK基本注解1.2.2JDK元注解1.3自定义注解二、使用自定义注解2.1案例一(获取类与方法上的注解值)2.2案例二(获取类属性上的注解属性值)2.3案例三(获取参数修饰注解对应的属性值)三、Aop自定义注解的应用一、Java注解1.1简介Java注解是附加在代

[论文阅读]Coordinate Attention for Efficient Mobile Network Design

摘要最近关于移动网络设计的研究已经证明了通道注意力(例如,theSqueeze-and-Excitationattention)对于提高模型的性能有显著的效果,但它们通常忽略了位置信息,而位置信息对于生成空间选择性注意图非常重要。在本文中,我们提出了一种新的移动网络注意力机制,将位置信息嵌入到通道注意力中,我们称之为“

【论文笔记】Baidu Apollo EM Motion Planner

文章目录AbstractI.INTRODUCTIONA.MultilaneStrategyB.Path-SpeedIterativeAlgorithmC.DecisionsandTrafficRegulationsII.EMPLANNERFRAMEWORKWITHMULTILANESTRATEGYIII.EMPLANN

nginx知识点详解:反向代理+负载均衡+动静分离+高可用集群

一、nginx基本概念1.nginx是什么,做什么事情?Nginx是一个高性能的HTTP和反向代理服务器,特点是占有内存少,并发能力强。Nginx转为性能优化而开发,能经受高负载考验。支持热部署,启动容易,运行时间长。2.反向代理正向代理:在客户端(浏览器)配置代理服务器,通过代理服务器进行互联网访问。反向代理:客户端

Python 08学习之文件操作

😀前言欢迎来到Python08学习之文件操作。在本文中,我们将介绍计算机中常见的文本文件和二进制文件,并探讨在Python中操作文件的步骤和相关函数/方法。通过学习本文,您将能够了解如何使用Python打开、读取、写入和关闭文件,以及如何按行读取文件内容。希望您能够通过本文提高您的Python文件操作能力,并且在实际

热文推荐