JAVA上传视频获取一张压缩图

前言

工作中需求需要根据上传的视频获取一张压缩图
度娘告诉我有三种方法:
1.javacv
2.ffmpeg
3.jcodec
前两种是对流媒体的使用,我最先使用javacv,根据我的需求大概只用的其中1/100的功能,而且引入的jar包太大,原本500M的包,压的最小也要40M,所以后期放弃了,改用jcodec实现,大概只要3-4M。
ffmpeg 没有实现。

javacv实现上传视频获取缩略图

javavc介绍
现在视频网站展示列表都是用img标签展示的,动图用的是gif,但是我们上传视频时并没有视屏封面,就这需要上传到服务器时自动成功封面并保存;(哎,我i又不是做视频网站的)
JavaCV 是一款开源的视觉处理库,基于GPLv2协议,对各种常用计算机视觉库封装后的一组jar包,封装了OpenCV、libdc1394、OpenKinect、videoInput和ARToolKitPlus等计算机视觉编程人员常用库的接口。

此方法的好处是不需要再服务器上安装插件,直接代码中就可以实现视频截取。
我们需要截取视频第一帧,主要用到了ffmpeg和opencv。

我用到的maven的目前最新javacv版本,1.4.2,它应该支持jdk1.7及以上,我项目用的还是jdk1.7
开发准备

idea开发工具
一个实现main方法demo(^ _ ^)(有maven啊)
jdk1.7以上(我的是1.8)



      org.bytedeco
      javacv-platform
      1.4.2

本来maven直接引用这段会自动下载依赖包,但是全部下载下载我看有500多兆,因为它包括了android,linux,macosx等。一个截取封面功能要给项目增加五百多兆内存这是不能容忍的。

我项目微服务整个才70M,加一个小功能要500M,要疯啊

所以在大神博客的指导下进行了精简!
导入依赖包


        
            org.bytedeco
            javacv
            1.4.2
            
                
                    org.bytedeco.javacpp-presets
                    *
                
            
        
        
            org.bytedeco.javacpp-presets
            opencv
            3.4.2-1.4.2
        
        
            org.bytedeco.javacpp-presets
            opencv
            3.4.2-1.4.2
            windows-x86_64
        
        <!– https://mvnrepository.com/artifact/org.bytedeco.javacpp-presets/ffmpeg –>
        
            org.bytedeco.javacpp-presets
            ffmpeg
            4.0.1-1.4.2
        
        
            org.bytedeco.javacpp-presets
            ffmpeg
            4.0.1-1.4.2
            windows-x86_64
        

你们猜一下还有多大,想知道吗?做个demo试一下就知道了!反正是小了很多。

这里的windows-x86_64是你在windows系统开发时使用的,如果你要放到linux上或者其他系统上:

    android-arm64
    android-x86
    android-x86_64
    ios-arm
    ios-arm64
    ios-x86
    ios-x86_64
    linux-armhf
    linux-arm64
    linux-ppc64le
    linux-x86
    linux-x86_64
    macosx-x86_64
    windows-x86
    windows-x86_64

根据上面的自己选吧!

代码实现

package cloud.eureka.client.controller;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class ImageUtil {

   /**
     * 生成截图
     *
     * @param filePath       视频文件本地路径
     * @param targerFilePath 目标文件夹
     * @param targetFileName 目标文件名
     * @return 图片文件路径
     * @throws Exception
     */

    public static String randomGrabberFFmpegImage(String filePath, String targerFilePath, String targetFileName)
            throws Exception {
        System.out.println(filePath);
        FFmpegFrameGrabber ff = FFmpegFrameGrabber.createDefault(filePath);
        ff.start();
        Frame f;
        int lenght = ff.getLengthInFrames();
        int i = 0;
        String path = null;
        while (i < lenght) {
            // 过滤前5帧,避免出现全黑的图片,依自己情况而定
            f = ff.grabFrame();
            if ((i > 200) && (f.image != null)) {
                path = doExecuteFrame(f, targerFilePath, targetFileName);
                break;
            }
            i++;
        }

        ff.stop();
        return path;
    }

    public static String doExecuteFrame(Frame f, String targerFilePath, String targetFileName) {

        if (null == f || null == f.image) {
            System.out.println("获取缩略图失败");
        }
        Java2DFrameConverter converter = new Java2DFrameConverter();
        String imageMat = "png";
        String FileName = targerFilePath + "/" + targetFileName + "." + imageMat;
        if(!new File(targerFilePath).exists()){
                new File(targerFilePath).mkdirs();
        }
        if(!new File(FileName).exists()){
            try {
                new File(FileName).createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        BufferedImage bi = converter.getBufferedImage(f);
        System.out.println("width:" + bi.getWidth());
        System.out.println("height:" + bi.getHeight());
        File output = new File(FileName);
 if(!new File(targerFilePath).exists()){
            new File(targerFilePath).exists();
        }

        
        try {
            ImageIO.write(bi, imageMat, output);
        } catch (IOException e) {
            System.out.println("缩略图写入文件夹失败");
        }
        return FileName;
    }

    public static void main(String[] args) throws Exception {
        String targerFilePath ="F:/home/xiao";
        String s = randomGrabberFFmpegImage("E:/weixi/WeChat Files/x_19930321/FileStorage/Video/2019-08/e694a1a231294ddac5092b6f6651c03e.mp4", targerFilePath, "213");


        ImgZipUtil.zipImageFile(s, targerFilePath + "/213.png", 80, 80, 0);

        System.out.println(s);
    }
}

ImgZipUtil是来将图片进行压缩的操作:

package cloud.eureka.client.controller;

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

public class ImgZipUtil {
    /**
     * 根据设置的宽高等比例压缩图片文件
先保存原文件,再压缩、上传 * * @param oldFilePath 要进行压缩的文件路径 * @param newFilePath 新文件路径 * @param width 宽度 //设置宽度时(高度传入0,等比例缩放) * @param height 高度 //设置高度时(宽度传入0,等比例缩放) * @param quality 质量 * @return 返回压缩后的文件的全路径 */ public static String zipImageFile(String oldFilePath, String newFilePath, int width, int height, float quality) { if (oldFilePath == null) { return null; } File oldFile = new File(oldFilePath); File newFile = new File(newFilePath); try { /* 对服务器上的临时文件进行处理 */ Image srcFile = ImageIO.read(oldFile); int w = srcFile.getWidth(null); int h = srcFile.getHeight(null); double bili; if (width > 0) { bili = width / (double) w; height = (int) (h * bili); } else { if (height > 0) { bili = height / (double) h; width = (int) (w * bili); } } String srcImgPath = newFile.getAbsoluteFile().toString(); String subfix = "jpg"; subfix = srcImgPath.substring(srcImgPath.lastIndexOf(".") + 1, srcImgPath.length()); BufferedImage buffImg; if (subfix.equals("png") && subfix.equals("jpg")) { buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); } else { buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); } Graphics2D graphics = buffImg.createGraphics(); graphics.setBackground(new Color(255, 255, 255)); graphics.setColor(new Color(255, 255, 255)); graphics.fillRect(0, 0, width, height); graphics.drawImage(srcFile.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null); ImageIO.write(buffImg, subfix, new File(srcImgPath)); } catch (IOException e) { e.printStackTrace(); } System.out.println(newFilePath); System.out.println(newFile.getAbsolutePath()); return newFile.getAbsolutePath(); } /** * 按设置的宽度高度压缩图片文件
先保存原文件,再压缩、上传 * * @param oldFile 要进行压缩的文件 * @param newFile 新文件 * @param width 宽度 * @param height 高度 * @param quality 质量 * @return 返回压缩后的文件的全路径 */ public static String zipWidthHeightImageFile(File oldFile, File newFile, int width, int height, float quality) { if (oldFile == null) { return null; } String newImage = null; try { /* 对服务器上的临时文件进行处理 */ Image srcFile = ImageIO.read(oldFile); String srcImgPath = newFile.getAbsolutePath(); System.out.println(srcImgPath); String subfix; subfix = srcImgPath.substring(srcImgPath.lastIndexOf(".") + 1, srcImgPath.length()); BufferedImage buffImg; if (subfix.equals("png")) { buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); } else { buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); } Graphics2D graphics = buffImg.createGraphics(); graphics.setBackground(new Color(255, 255, 255)); graphics.setColor(new Color(255, 255, 255)); graphics.fillRect(0, 0, width, height); graphics.drawImage(srcFile.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null); ImageIO.write(buffImg, subfix, new File(srcImgPath)); } catch (IOException e) { e.printStackTrace(); } return newImage; } /*** * 按指定的比例缩放图片 * @param sourceImagePath * 源地址 * @param destinationPath * 改变大小后图片的地址 * @param scale * 缩放比例,如1.2 */ public static void scaleImage(String sourceImagePath, String destinationPath, double scale, String format) throws IOException { File file = new File(sourceImagePath); BufferedImage bufferedImage; bufferedImage = ImageIO.read(file); int width = bufferedImage.getWidth(); int height = bufferedImage.getHeight(); width = (int) (width * scale); height = (int) (height * scale); Image image = bufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH); BufferedImage outputImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics graphics = outputImage.getGraphics(); graphics.drawImage(image, 0, 0, null); graphics.dispose(); ImageIO.write(outputImage, format, new File(destinationPath)); } public static boolean DeleteFolder(String sPath) { boolean flag = false; File file = new File(sPath); // 判断目录或文件是否存在 if (!file.exists()) { // 不存在返回 false return false; } else { if (file.isFile() && file.exists()) { // 路径为文件且不为空则进行删除 file.delete(); flag = true; } } return flag; } }

以上是使用javavc进行视频上传获取缩略图,不过要根据自己的实际情况进行相应的修改,啊!!!


jcodec实现上传视频获取缩略图

其实我在javavc已经实现了上传视频获取缩略图功能效果,但是依赖的jar包还是有点大,毕竟我也不是做视频网站的,感觉还是不太理想。

项目中需要获取视频的缩略图,并发和性能要求不够高 ,找了一个基本满足需求,比较轻量的工具 jcodec 。
相同的环境导入依赖包:

 		
            org.jcodec
            jcodec
            0.2.4
        
        
            org.jcodec
            jcodec-javase
            0.2.4
        

2.4目前是最新的,不过大部分使用的是2.3,稳定

代码实现

package cloud.eureka.client.util;

import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;

public final class ImageUtils {

    //缩略图默认长宽限制
    private static final int THUMBNAIL_DEFAULT_LIMIT = 400;

    private ImageUtils() { }

    /**
     * 根据长宽值缩放
     * @author sunk
     */
    public static BufferedImage scaleByWh(BufferedImage source, int width, int height) {
        return getBufferedImageLocal(source.getScaledInstance(width, height, Image.SCALE_SMOOTH));
    }

    /**
     * 根据长宽限制缩放
     * 

限制较大的一边 * @author sunk */ public static BufferedImage scaleByWhLimit(BufferedImage source, int limit) { int scaleW = -1; int scaleH = -1; if (source.getWidth() > source.getHeight()) { scaleW = limit; } else { scaleH = limit; } return scaleByWh(source, scaleW, scaleH); } /** * 根据比例缩放 * @author sunk */ public static BufferedImage scaleByRatio(BufferedImage source, double ratio) { int w = (int) (source.getWidth() * ratio); int h = (int) (source.getHeight() * ratio); return scaleByWh(source, w, h); } /** * 将 Image 转为 BufferedImage * @author sunk */ public static BufferedImage getBufferedImage(Image img) { if (img instanceof BufferedImage) { return (BufferedImage) img; } BufferedImage bufImage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB); Graphics2D g2d = bufImage.createGraphics(); g2d.drawImage(img, 0, 0, null); g2d.dispose(); return bufImage; } /** * 将 Image 转为 BufferedImage * @author sunk */ public static BufferedImage getBufferedImageLocal(Image img) { if (img instanceof BufferedImage) { return (BufferedImage) img; } GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice gd = ge.getDefaultScreenDevice(); GraphicsConfiguration gc = gd.getDefaultConfiguration(); BufferedImage bufImage = gc.createCompatibleImage(img.getWidth(null), img.getHeight(null)); Graphics2D g2d = bufImage.createGraphics(); g2d.drawImage(img, 0, 0, null); g2d.dispose(); return bufImage; } /** * 获取缩略图 * @author sunk */ public static ByteArrayOutputStream getThumbnail(BufferedImage sourceBi, int limit) throws IOException { //缩放 BufferedImage scaledBi = scaleByWhLimit(sourceBi, limit); //压缩 ByteArrayOutputStream compressedOs = new ByteArrayOutputStream(); ImageWriter jpgWriter = ImageIO.getImageWritersByFormatName("jpg").next(); ImageWriteParam jpgWriteParam = jpgWriter.getDefaultWriteParam(); jpgWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); jpgWriteParam.setCompressionQuality(0.5f); jpgWriter.setOutput(ImageIO.createImageOutputStream(compressedOs)); jpgWriter.write(null, new IIOImage(scaledBi, null, null), jpgWriteParam); return compressedOs; } /** * 获取缩略图 * @author sunk */ public static ByteArrayOutputStream getThumbnail(BufferedImage sourceBi) throws IOException { return getThumbnail(sourceBi, THUMBNAIL_DEFAULT_LIMIT); } /** * 获取缩略图 * @author sunk */ public static ByteArrayOutputStream getThumbnail(File sourceFile, int limit) throws IOException { return getThumbnail(ImageIO.read(sourceFile), limit); } /** * 获取缩略图 * @author sunk */ public static ByteArrayOutputStream getThumbnail(File source) throws IOException { return getThumbnail(ImageIO.read(source)); } }

package cloud.eureka.client.util;

import cloud.eureka.client.controller.ImgZipUtil;
import org.jcodec.api.FrameGrab;
import org.jcodec.api.JCodecException;
import org.jcodec.common.model.Picture;
import org.jcodec.scale.AWTUtil;

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

public final class VideoUtils {

    private static final int THUMB_FRAME = 5;

    private VideoUtils() { }

    /**
     * 获取视频指定帧
     * @author sunk
     */
    public static BufferedImage getFrame (File file, int frameNumber)
            throws IOException, JCodecException {

        System.out.println(file.exists());
        Picture picture = FrameGrab.getFrameFromFile(file, frameNumber);
        return AWTUtil.toBufferedImage(picture);
    }

    /**
     * 获取缩略图
     * @author sunk
     */
    public static ByteArrayOutputStream getThumbnail(File file,
                                                     int limit) throws IOException, JCodecException {

        BufferedImage frameBi = getFrame(file, THUMB_FRAME);
        return ImageUtils.getThumbnail(frameBi, limit);
    }

    /**
     * 获取缩略图
     * @author sunk
     */
    public static ByteArrayOutputStream getThumbnail(File file)
            throws IOException, JCodecException {

        BufferedImage frameBi = getFrame(file, THUMB_FRAME);
        String imageMat="png";
        String targerFilePath="f://img//";
        File outputDes = new File(targerFilePath);
        if(!outputDes.exists()){
            outputDes.mkdirs();
        }
        String imgName="22.png";
        File imgFile=new File(targerFilePath+imgName);
        if(!imgFile.exists()){
            imgFile.createNewFile();
        }
        ImageIO.write(frameBi, imageMat, imgFile);
        String ysImg= targerFilePath + "213.png";
        File ysFile=new File(ysImg);
        if(!ysFile.exists()){
            ysFile.createNewFile();
        }
        System.out.println(file.getPath());
        ImgZipUtil.zipImageFile(imgFile.getPath(), ysImg, 80, 80, 0);
        return ImageUtils.getThumbnail(frameBi);
    }
}

package cloud.eureka.client.controller;

import cloud.eureka.client.util.VideoUtils;
import org.jcodec.api.JCodecException;

import java.io.File;
import java.io.IOException;

public class Main {


    public static void main(String [] args){

        File file= new File("F:\\24.mp4");
        try {
          VideoUtils.getThumbnail(file);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JCodecException e) {
            e.printStackTrace();
        }

    }

}

以上是使用jcodec进行视频上传获取缩略图,不过要根据自己的实际情况进行相应的修改,啊!!!

注:

Picture picture = FrameGrab.getFrameFromFile(file, frameNumber);

如果在这一步报错的话,看看你上传的视屏是否正确(如地址,有无数据等)

引用的博客:
javavc:
javavc使用的方法
javavc依赖jar介绍以及精简

jcodec:
jcodec介绍以及使用

你可能感兴趣的:(utils)