工作中需求需要根据上传的视频获取一张压缩图
度娘告诉我有三种方法:
1.javacv
2.ffmpeg
3.jcodec
前两种是对流媒体的使用,我最先使用javacv,根据我的需求大概只用的其中1/100的功能,而且引入的jar包太大,原本500M的包,压的最小也要40M,所以后期放弃了,改用jcodec实现,大概只要3-4M。
ffmpeg 没有实现。
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进行视频上传获取缩略图,不过要根据自己的实际情况进行相应的修改,啊!!!
其实我在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介绍以及使用