Java提取视频中的指定桢数生成视频缩略图的解决方案

1.1 开发背景

在Java后端开发中,当返回给前端或移动端视频列表的时候,客户端往往并不直接加载所有视频,而是先加载后端返回的视频缩略图列表。

然而这个视频缩略图列表从哪里来呢?总不能让上传视频的用户自己上传吧?

答案当然是不能, 我们Java后端其实是有办法解决这个问题的。

  • 一种方法是使用FFmpeg 视频定时截图工具,这种局限在必须部署在windows 服务器。

  • 另外一种是通过程序Java 代码编程方式使用JavaCV 类库实现。

那么什么是JavaCV 呢?

  • JavaCV是对各种常用计算机视觉库的封装后的一组jar包,其中封装了FFmpegOpenCV等计算机视觉编程人员常用库的接口,可以通过其中的Utility类方便的在包括Android在内的Java平台上调用这些接口。
  • 最开始Javacvgooglecode下面的一个项目,后来迁移到了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");
测试二在尝试获取腾讯视频地址的时候由于无法获取到视频真实文件地址,导致报错失败。

1.3 利用阿里云OOS获取视频缩略图

今天偶然发现的一个新操作,凡是存放在阿里云OOS上的视频,都可以很方便的获取视频缩略图。

操作示例如下:

比如这是阿里云OOS上一个视频:http://a-image-demo.oss-cn-qingdao.aliyuncs.com/demo.mp4

那么获取视频第一秒的png格式的缩略图缩略图就是:http://a-image-demo.oss-cn-qingdao.aliyuncs.com/demo.mp4?x-oss-process=video/snapshot,t_10000,m_fast,w_400,h_400,f_png

参数描述取值范围
t截图时间单位ms,[0,视频时长]
w截图宽度,如果指定为0则自动计算像素值:[0,视频宽度]
h截图高度,如果指定为0则自动计算,如果w和h都为0则输出为原视频宽高像素值:[0,视频高度]
m截图模式,不指定则为默认模式,根据时间精确截图,如果指定为fast则截取该时间点之前的最近的一个关键帧枚举值:fast
f输出图片格式枚举值:jpg、png

本篇完~

参考资料

技术宅星云 CSDN认证博客专家 Java Spring MySQL
技术宅星云(网名),英文名fairy,CSDN博客专家,先后曾在外企惠普,央企中航信工作, 目前担任北京蛙跳科技有限公司后端高级开发工程师,负责公司短视频应用后台,擅长JAVA后端技术.
已标记关键词 清除标记
相关推荐
<div><p>Hi ,</p> <p>Sorry for troubling you again. Finally I've been able to set up JavaCV in Android Studio (version 1.1) using the add <code>.jar</code> method so my <em>build.gradle</em> looks like this:</p> <pre><code> groovy compile files('libs/javacpp.jar') compile files('libs/javacv.jar') compile files('libs/ffmpeg.jar') compile files('libs/ffmpeg-android-arm.jar') </code></pre> <p>I'm trying to use the <code>FFmpegFrameGrabber</code> as below:</p> <pre><code> java String path = "/storage/emulated/0/DCIM/Camera/VID_20160422_173902.mp4"; FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(path); grabber.setFormat("mp4"); try { grabber.start(); grabber.setFrameNumber((int) (1000 * grabber.getFrameRate() / 1000)); //get current frame based on framerate Frame frame = grabber.grabImage(); AndroidFrameConverter androidFrameConverter = new AndroidFrameConverter(); Bitmap bitmap = androidFrameConverter.convert(frame); imgView.setImageBitmap(bitmap); } catch (Exception e) { e.printStackTrace(); }finally { try { grabber.stop(); } catch (FrameGrabber.Exception e) { e.printStackTrace(); } } </code></pre> <p>However I got hit with this error at <code>grabber.start()</code>:</p> <pre><code> org.bytedeco.javacv.FrameGrabber$Exception: avformat_open_input() error -13: Could not open input "/storage/emulated/0/DCIM/Camera/VID_20160422_173902.mp4". (Has setFormat() been called?) at org.bytedeco.javacv.FFmpegFrameGrabber.startUnsafe(FFmpegFrameGrabber.java:432) at org.bytedeco.javacv.FFmpegFrameGrabber.start(FFmpegFrameGrabber.java:380) at co.example.test.MainActivity.onCreate(MainActivity.java:27) at android.app.Activity.performCreate(Activity.java:6251) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476) at android.app.ActivityThread.-wrap11(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:148) at android.app.ActivityThread.main(ActivityThread.java:5417) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) </code></pre> <p>I've even tried to use the <em>ffmpeg-android-arm.jar</em> and <em>ffmpeg.jar</em> from JavaCV 1.2 but it just crash with another stack trace. I've read this #68, but my video file is neither big or long (18.37mb, 720p, 12 seconds). </p> <p>What would possibly the solution for my issue? Thank you so much for your time.</p> <p><strong>Note:</strong> for anyone still having trouble setting JavaCV up in Android Studio, I've created a <a href="https://gist.github.com/vxhviet/cd39621ae768be07dca30e399c36c034">Gist</a> providing step by step instruction.</p><p>该提问来源于开源项目:bytedeco/javacv</p></div>
©️2020 CSDN 皮肤主题: 终极编程指南 设计师:CSDN官方博客 返回首页
实付 59.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值