一. 前端代码
前端使用layui
框架实现视频文件上传。
1.1 html展示
editGoods.html
<div class="layui-form-item" id="backgBg_div">
<label class="layui-form-label">视频上传:</label>
<div class="img-list-upload fl mt10" id="bannerImagePicture">
<img src="../../../../static/images/add_upload_img.png" class="img-list-icon" alt="">
<div id="bannerImagephoto" class="img-list-letter">上传视频</div>
</div>
<div class="layui-form-text fl">
<div class="layui-upload">
<blockquote class="layui-elem-quote layui-quote-nm preview-img">
<div class="layui-upload-list" id="uploadlist2"></div>
</blockquote>
</div>
</div>
<div id="imgweiPathBanner"></div>
<div style="color: #999999;font-size: 12px;margin-left: 200px">
<div style="height: 30px;line-height: 30px">视频上传要求:</div>
<div>1,视频大小不大于5M,视频格式仅支持mp4格式。</div>
<div>2,只允许上传一条视频,且默认显示在第一位。</div>
<div>3,时长暂时无限制,大小小于5M即可(暂不支持后台视频预览)。</div>
</div>
</div>
1.2 js逻辑
editGoods.js
//上传视频
base.updateVideo('#bannerImagePicture', '#uploadlist2', '#imgweiPathBanner', 2,base.apiVipMall() +
'/documents/saveVideo',92,92,'../../../../static/plugin/layui/img/delete.svg');
base.js
通用脚本位置
/**
* 按照顺序多图片上传修改默认图片长宽
* @param a 点击按钮的Id标识
* @param b 图片展示的Id
* @param c 图片链接存储位置 隐藏域的Id
* @param d 传入的数字,用来区分不同类型的图片(同一页面不重复即可)
* @param url 文件上传服务器接口
* @param height 缩略图页面展示高度
* @param width 缩略图页面展示宽度
* @param imgUrl 删除按钮图片
*/
//上传视频
updateVideo:function(a, b, c, d,url,height,width,imgUrl) {
var fileSizeLimit = ''; //文件大小限制 默认清空操作
upload.render({
elem:a,
url: base.apiVipMall() + '/documents/saveVideo',
// field: "layuiVideo",
// data: {"dir": "media"},
accept: 'video', //视频
exts: 'mp4',//上传视频支持的格式
// exts: 'mp4|avi|rmvb|mkv',//上传视频支持的格式
headers: {
'xxl_sso_sessionid': window.sessionStorage.getItem('sessionid')
},
choose: function (obj) { //上传前选择回调方法
obj.preview(function (index, file, result) {
fileSizeLimit = file.size; //上传视频的大小
})
},
done: function (res, index) {
var sign = d + "_imgsign" + index;
//如果上传
if (res.code != 200) {
return layer.msg('上传失败');
}
if(fileSizeLimit > 1024*1024*5){
var sign = $(b).children("div").attr("sign"); //移除显示图片
$("[sign='" + sign + "']").remove()
$(c).html('');
layer.msg('文件大小不得超过5M,请重新上传', {icon: 2});
return
}
var str = '<div sign="' + sign + '" style="width:'+width+"px"+';height:'+height+"px"+';margin-left: 5px;
float:left; margin-right: 5px;margin-bottom: 35px;"><img src="' + res.firstFrame.filePath + '" name="videoImg"
class="layui-upload-img" width='+width+"px"+' height='+height+"px"+' sign="' + sign + '" ><div class="showdiv">
<img onclick="delimg(this)" sign="' + sign + '" class="center" src="'+imgUrl+'" /></div></div>';
$(b).html(str);
//上传成功
var videoValue = "videoValue_" + d;
var input = "<input type='hidden' name=" + videoValue + " class='weiPic' sign=" + sign + "
value=" + res.result.filePath + " />"
$(c).html(input);
} ,
error: function () {
if(fileSizeLimit > 1024*1024*5){
var sign = $(b).children("div").attr("sign"); //移除显示图片
$("[sign='" + sign + "']").remove();
$(c).html('');
layer.msg('文件大小不得超过5M,请重新上传', {icon: 2});
return
}
}
})
},
二. 服务端业务
服务端使用spring boot 框架,引用视频处理依赖
<!--start:视频处理依赖-->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.4.3</version>
</dependency>
<dependency>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>ffmpeg-platform</artifactId>
<version>4.0.2-1.4.3</version>
</dependency>
<!--end:视频处理依赖-->
2.1 Controller层
@Autowired
private SaveFileUtil saveFileUtil;
@CrossOrigin(methods = RequestMethod.POST, origins = "*")
@PostMapping(value = "/saveVideo", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Map<String,Object> saveVideo(MultipartFile file){
Map<String,Object> result= Maps.newHashMap();
saveFileUtil.saveFile(file, result, true);
return result;
}
2.2 util 工具 主逻辑
package com.xlcloud.business.xxx.util.document;
import com.xlcloud.business.vip.util.exceptions.BusinessException;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
/**
* @author charles
* @createTime 2020/8/25 15:07
* @description 视频帧处理工具
*/
public class VideoFrameUtil {
private static final Logger logger = LoggerFactory.getLogger(VideoFrameUtil.class);
//视频文件路径:
public static String videoPath = "D:/video";
/**
* @author charles
* @createTime 2020/8/26 10:18
* @desc 通过视频流 获取视频的第一帧
*/
public static InputStream grabberVideoFramer(InputStream inputStream) throws IOException{
FFmpegFrameGrabber fFmpegFrameGrabber = new FFmpegFrameGrabber(inputStream);
return grabberVideoFramer(fFmpegFrameGrabber);
}
/**
* @author charles
* @createTime 2020/8/27 9:12
* @desc 获取本地视频文件 截取第一帧
*/
public static InputStream grabberVideoFramer(String videoFileName) throws IOException {
FFmpegFrameGrabber fFmpegFrameGrabber = new FFmpegFrameGrabber(videoPath + "/" + videoFileName);
return grabberVideoFramer(fFmpegFrameGrabber);
}
/**
* @author charles
* @createTime 2020/8/27 9:14
* @desc 获取视频时长
*/
public static double videoTimeLength(FFmpegFrameGrabber fFmpegFrameGrabber){
int ftp = fFmpegFrameGrabber.getLengthInFrames();
return ftp / fFmpegFrameGrabber.getFrameRate();
}
/**
* @author charles
* @createTime 2020/8/26 10:19
* @desc 获取视频的第一帧
*/
private static InputStream grabberVideoFramer(FFmpegFrameGrabber fFmpegFrameGrabber) throws IOException{
fFmpegFrameGrabber.start();
int ftp = fFmpegFrameGrabber.getLengthInFrames();
if (ftp > 0){
// 获取一帧
Frame frame = fFmpegFrameGrabber.grabImage();
BufferedImage bufferedImage = FrameToBufferedImage(frame);
// 图片输出流
InputStream inputStream = bufferedImageToInputStream(bufferedImage);
fFmpegFrameGrabber.stop();
return inputStream;
}
return null;
}
/**
* @author charles
* @createTime 2020/8/27 8:54
* @desc 本地测试截取任意帧
*/
public static void grabberVideoFramerLocal(String videoFileName, int frameNum) throws IOException{
FFmpegFrameGrabber fFmpegFrameGrabber = new FFmpegFrameGrabber(videoPath + "/" + videoFileName);
fFmpegFrameGrabber.start();
double length = videoTimeLength(fFmpegFrameGrabber);
if (length < frameNum){
throw new BusinessException("截取的视频帧不能超过视频总帧数!");
}
//Frame对象
Frame frame = null;
// 输出视频的每一帧
for (int i = 1; i <= length; i++) {
//获取帧
frame = fFmpegFrameGrabber.grabImage();
if (i == frameNum){
//文件绝对路径+名字
String fileName = "D:/video/img_" + i + ".png";
//文件储存对象
File outPut = new File(fileName);
if (frame != null) {
ImageIO.write(FrameToBufferedImage(frame), "png", outPut);
}
}
}
fFmpegFrameGrabber.stop();
}
/**
* @author charles
* @createTime 2020/8/26 10:32
* @desc 转换对象
*/
public static BufferedImage FrameToBufferedImage(Frame frame) {
//创建BufferedImage对象
Java2DFrameConverter converter = new Java2DFrameConverter();
BufferedImage bufferedImage = converter.getBufferedImage(frame);
return bufferedImage;
}
/**
* 将BufferedImage转换为InputStream
* @param image
* @return
*/
public static InputStream bufferedImageToInputStream(BufferedImage image){
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ImageIO.write(image, "png", os);
return new ByteArrayInputStream(os.toByteArray());
} catch (IOException e) {
logger.error("获取视频帧失败", e);
}
return null;
}
/**
* @author charles
* @createTime 2020/8/27 8:58
* @desc 测试
*/
public static void main(String[] args) throws IOException {
// 获取视频的第一帧测试
String videoFileName = "1586490_A.mp4";
// InputStream inputStream = grabberVideoFramer(videoFileName);
// String test = new SaveFileUtil().saveImg(inputStream, "test11111.png");
// System.out.println(test);
// 获取视频的任意帧
grabberVideoFramerLocal(videoFileName, 10);
}
}