FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。
FFmpeg在Linux平台下开发,但它同样也可以在其它操作系统环境中编译运行,包括Windows、Mac OS X等。这个项目最早由Fabrice Bellard发起,2004年至2015年间由Michael Niedermayer主要负责维护。许多FFmpeg的开发人员都来自MPlayer项目,而且当前FFmpeg也是放在MPlayer项目组的服务器上。项目的名称来自MPEG视频编码标准,前面的"FF"代表"Fast Forward"。
官网地址
git地址
因为 FFmpeg 依赖做了多平台兼容,整体的包有200多MB,这边我们只取我们需要的功能
(查看依赖树会发现就像老太婆的裹脚布)
<dependency>
<groupId>org.bytedecogroupId>
<artifactId>javacppartifactId>
<version>1.5.8version>
dependency>
<dependency>
<groupId>org.bytedecogroupId>
<artifactId>javacvartifactId>
<version>1.5.8version>
<exclusions>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>javacppartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>opencvartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>flycaptureartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>libdc1394-platformartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>libfreenectartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>libfreenect2artifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>librealsenseartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>librealsense2artifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>artoolkitplusartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>flandmarkartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>leptonicaartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>tesseractartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.bytedecogroupId>
<artifactId>javacv-platformartifactId>
<version>1.5.8version>
<exclusions>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>ffmpeg-platformartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>openblas-platformartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>opencv-platformartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>flycapture-platformartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>libdc1394-platformartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>libfreenect-platformartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>libfreenect2-platformartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>librealsense-platformartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>librealsense2-platformartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>artoolkitplus-platformartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>flandmark-platformartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>leptonica-platformartifactId>
exclusion>
<exclusion>
<groupId>org.bytedecogroupId>
<artifactId>tesseract-platformartifactId>
exclusion>
<exclusion>
<artifactId>javacppartifactId>
<groupId>org.bytedecogroupId>
exclusion>
<exclusion>
<artifactId>openblasartifactId>
<groupId>org.bytedecogroupId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.bytedecogroupId>
<artifactId>ffmpegartifactId>
<version>5.1.2-1.5.8version>
<classifier>windows-x86_64classifier>
dependency>
<dependency>
<groupId>org.bytedecogroupId>
<artifactId>ffmpegartifactId>
<version>5.1.2-1.5.8version>
<classifier>linux-x86_64classifier>
dependency>
package com.ruoyi.common.utils.file;
import org.bytedeco.ffmpeg.global.avcodec;
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.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 获取视频首帧图片用于界面展示
*
* @author zzxjson
* 基本信息描述:https://www.cnblogs.com/liangjingfu/p/12858018.html
*
* Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'F:\temporary\123456789.mp4':
* Metadata:
* major_brand : isom
* minor_version : 512
* compatible_brands: isomiso2avc1mp41
* encoder : Lavf58.12.100
* description : Tencent SID MTS
* Duration: 00:04:13.21, start: 0.000000, bitrate: 3167 kb/s
* Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 3033 kb/s, 24 fps, 24 tbr, 90k tbn, 48 tbc (default)
* Metadata:
* handler_name : VideoHandler
* Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default)
* Metadata:
* handler_name : SoundHandler
*/
public class FFmpegUtil {
/**
* @throws
* @Title: getTempPath
* @Description: 生成视频的首帧图片方法
* @author: Zing
* @param: @param tempPath 生成首帧图片的文件地址
* @param: @param filePath 传进来的线上文件
* @param: @return
* @param: @throws Exception
* @return: boolean
*/
public static Video getTempPath(File targetFile, File file2) throws Exception {
Video video = new Video(false,"00:00:00:00");
if (!targetFile.getParentFile().exists()) {
targetFile.getParentFile().mkdirs();
}
System.out.println("确认文件是否是视频...");
//判断文件是否为视频
if (isVideo(file2.getAbsolutePath())) {
System.out.println("确认成功!");
//判断文件是否存在
if (file2.getParentFile().exists()) {
FFmpegFrameGrabber ff = new FFmpegFrameGrabber(file2);
//获取时长
video.setDuration(durationlFormat(ff));
//转缩略图
video.setGetThumbnail(getThumbnail(file2, targetFile));
ff.close();
} else {
System.out.println("路径内容错误!" + file2.getParentFile());
}
} else {
System.out.println("确认失败!");
}
return video;
}
private static Boolean getThumbnail(File sourceFile, File targetFile) throws FrameGrabber.Exception {
FFmpegFrameGrabber ff = null;
Frame frame = null;
try {
ff = new FFmpegFrameGrabber(sourceFile);
ff.setVideoCodec(avcodec.AV_CODEC_ID_MJPEG);
ff.start();
//转缩略图
int length = ff.getLengthInFrames();
int i = 0;
while (i < length) {
// 过滤前20帧,避免出现全黑的图片()
frame = ff.grabFrame().clone();
if ((i > 20) && (frame.image != null)) {
break;
}
i++;
}
int tmpWidth = frame.imageWidth;
int tmpHeight = frame.imageHeight;
// 对截取的帧进行等比例缩放
int width = 300;
int height = (int) (((double) width / tmpWidth) * tmpHeight );
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
BufferedImage renderedImage = new Java2DFrameConverter().getBufferedImage(frame);
bi.getGraphics().drawImage(renderedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null);
if (ImageIO.write(bi, "png", targetFile)) {
System.out.println("输出图片成功!");
return true;
} else {
System.out.println("输出图片失败!");
return false;
}
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
if (ff != null) {
try {
ff.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (frame != null) {
frame.close();
}
}
}
/**
* @throws
* @Title: isVideo
* @Description:判断是不是视频
* @author: Zing
* @param: @param path 文件路径
* @param: @return
* @return: boolean true是视频 false非视频
*/
public static boolean isVideo(String path) {
//设置视频后缀
List<String> typeList = new ArrayList<>();
typeList.add("mp4");
typeList.add("flv");
typeList.add("avi");
typeList.add("rmvb");
typeList.add("rm");
typeList.add("wmv");
//获取文件名和后缀
String suffix = path.substring(path.lastIndexOf(".") + 1);
System.out.println(suffix);
for (String type : typeList) {
//统一为大写作比较
if (type.toUpperCase().equals(suffix.toUpperCase())) {
return true;
}
}
return false;
}
/**
* 格式化时长
* @return 00:00:00:00
*/
private static String durationlFormat(FFmpegFrameGrabber ff) throws FrameGrabber.Exception {
try{
ff.start();
long duration = ff.getLengthInTime() / 1000;
ff.close();
return String.format("%02d:%02d:%02d",
TimeUnit.MILLISECONDS.toHours(duration),
TimeUnit.MILLISECONDS.toMinutes(duration) % TimeUnit.HOURS.toMinutes(1),
TimeUnit.MILLISECONDS.toSeconds(duration) % TimeUnit.MINUTES.toSeconds(1));
}catch (Exception e){
throw e;
}finally {
ff.close();
}
}
public static class Video{
private Boolean getThumbnail;
private String duration;
public Video(Boolean getThumbnail, String duration) {
this.getThumbnail = getThumbnail;
this.duration = duration;
}
public Boolean getGetThumbnail() {
return getThumbnail;
}
public void setGetThumbnail(Boolean getThumbnail) {
this.getThumbnail = getThumbnail;
}
public String getDuration() {
return duration;
}
public void setDuration(String duration) {
this.duration = duration;
}
}
}
public AjaxResult add(MultipartFile file){
//临时文件位置
String newPath = profilePath + "wareTemporary/";
String originalFilename = file.getOriginalFilename();
UUID uuid = UUID.randomUUID();
//临时视频地址
String temporaryVideoPath = newPath + uuid + originalFilename.substring(originalFilename.lastIndexOf("."));
// 将上传的文件保存到临时文件中
File tempFile = FileUtil.file(temporaryVideoPath);
//临时图片地址
String temporaryThumbnailPath = newPath + uuid + ".png";
File thumbnail = new File(temporaryThumbnailPath);
try{
file.transferTo(tempFile);
//获取课件的时长和缩略图
FFmpegUtil.Video tempPath = FFmpegUtil.getTempPath(thumbnail,tempFile);
if (!tempPath.getGetThumbnail()){
return AjaxResult.error("确认文件类型出错");
}
//上传缩略图
String thumbnailUrl = minioUtil.uploadFile(thumbnail);
//上传视频
String videoUrl = minioUtil.uploadFile(tempFile);
return AjaxResult.success(videoUrl).put("thumbnail",thumbnailUrl).put("duration",tempPath.getDuration());
}catch (Exception e){
logger.error("上传文件失败",e);
return AjaxResult.error(e.getMessage());
}finally {
//删除临时文件
thumbnail.delete();
tempFile.delete();
}
}