背景:遇到一个项目就是上传视频文件不限格式,需要全部统一转成mp4格式。具体细节是 1.前端展示的视频不能是从头加载的,需要像某讯那样快进到哪里,从哪个节点开始加载,实现无卡顿播放,2,后台获取时长,大小等数据信息,四张视频截图封面,从其中选择一张作为封面。
直接上代码:视频获取时长大小,截图,视频转换
import com.alibaba.fastjson.JSONObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ConvertVideo {
private static String mplayerPath; //= "C:/watermark/mencoder.exe";
private static String ffmpegPath; //= "C:/watermark/ffmpeg-4.3.1-2021-01-01-essentials_build/bin/ffmpeg.exe";
private static String filepath; //= "D:/minio/data/";
public static void setMplayerPath(String mplayerPath) {
ConvertVideo.mplayerPath = mplayerPath;
}
public static void setFfmpegPath(String ffmpegPath) {
ConvertVideo.ffmpegPath = ffmpegPath;
}
public static void setFilepath(String filepath) {
ConvertVideo.filepath = filepath;
}
/**
* 视频格式转换入口
*
* @param inputPath 源文件 路径
* @param outputPath 目标文件 路径
* @param outputPath1 中间文件地址 路径
* @return
*/
public synchronized static boolean process(String inputPath, String outputPath ,String outputPath1) {
int type = checkContentType(inputPath);
boolean status = false;
Long old = System.currentTimeMillis();
System.out.println("old--------" + old);
if (type == 0) {
System.out.println("直接转成MP4格式");
status = processMP4(inputPath, outputPath);// 直接转成flv格式
} else if (type == 1) {
String avifilepath = processAVI(inputPath, outputPath1);
System.out.println(avifilepath);
if (avifilepath == null){
return false;// 没有得到avi格式
}
status = processMP4(avifilepath, outputPath);// 将avi转成flv格式
}
Long yong = System.currentTimeMillis();
System.out.println("消耗时间----------" + (yong-old));
return status;
}
/**
* 判断文件类型.执行不同的转换流程
*
* @param inputPath 源文件路径
* @return
*/
private static int checkContentType(String inputPath) {
String type = inputPath.substring(inputPath.lastIndexOf(".") + 1, inputPath.length())
.toLowerCase();
// ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
if (type.equals("avi")) {
return 0;
} else if (type.equals("mpg")) {
return 0;
} else if (type.equals("wmv")) {
return 0;
} else if (type.equals("3gp")) {
return 0;
} else if (type.equals("mov")) {
return 0;
} else if (type.equals("mp4")) {
return 0;
} else if (type.equals("asf")) {
return 0;
} else if (type.equals("asx")) {
return 0;
} else if (type.equals("flv")) {
return 0;
}
// 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等),
// 可以先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式.
else if (type.equals("wmv9")) {
return 1;
} else if (type.equals("rm")) {
return 1;
} else if (type.equals("rmvb")) {
return 1;
}else if (type.equals("dat")) {
return 1;
}
return 9;
}
/**
* 判断源文件是否存在
*
* @param path 文件路径
* @return
*/
private static boolean checkfile(String path) {
File file = new File(path);
if (!file.isFile()) {
return false;
}
return true;
}
// 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等), 可以先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式.
private static String processAVI(String inputPath, String outputPath1) {
List<String> commend = new ArrayList<>();
commend.add(mplayerPath);
//commend.add("C:/watermark/mencoder.exe");
commend.add(inputPath);
commend.add("-oac");
commend.add("mp3lame");
commend.add("-lameopts");
commend.add("preset=64");
commend.add("-ovc");
commend.add("xvid");
commend.add("-xvidencopts");
commend.add("bitrate=600");
commend.add("-of");
commend.add("avi");
commend.add("-o");
commend.add(outputPath1);
try {
ProcessBuilder builder = new ProcessBuilder();
Process process = builder.command(commend).redirectErrorStream(true).start();
new PrintStream(process.getInputStream()).start();
new PrintStream(process.getErrorStream()).start();
process.waitFor();
process.destroy();
return outputPath1;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
// ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等) 转换为MP4
private static boolean processMP4(String inputPath, String outputPath) {
if (!checkfile(inputPath)) {
System.out.println(inputPath + " is not file");
return false;
}
List<String> command = new ArrayList<>();
command.add(ffmpegPath);
//command.add("C:/watermark/ffmpeg-4.3.1-2021-01-01-essentials_build/bin/ffmpeg.exe");
command.add("-i");
command.add(inputPath);
command.add("-c:v");
command.add("libx264");
command.add("-mbd");
command.add("0");
command.add("-c:a");
command.add("aac");
command.add("-strict");
command.add("-2");
command.add("-pix_fmt");
command.add("yuv420p");
command.add("-movflags");
command.add("faststart");
command.add(outputPath);
try {
Process videoProcess = new ProcessBuilder(command).redirectErrorStream(true).start();
new PrintStream(videoProcess.getErrorStream()).start();
new PrintStream(videoProcess.getInputStream()).start();
videoProcess.waitFor();
videoProcess.destroy();
System.out.println(outputPath);
return true;
} catch (Exception e) {
return false;
}
}
//map4截取图片
public synchronized static boolean processImg(String veido_path, String image_path,String minao) {
File file = new File(veido_path);
if (!file.exists()) {
System.err.println("路径[" + veido_path + "]对应的视频文件不存在!");
return false;
}
List<String> commands = new ArrayList<String>();
commands.add(ffmpegPath);
commands.add("-i");
commands.add(veido_path);
commands.add("-y");
commands.add("-f");
commands.add("image2");
commands.add("-ss");
System.out.println(String.valueOf(minao));
commands.add(minao);// 这个参数是设置截取视频多少秒时的画面
// commands.add("-t");
// commands.add("0.001");
commands.add("-s");
commands.add("700x700");//尺寸
/*commands.add(veido_path.substring(0, veido_path.lastIndexOf("."))
.replaceFirst("vedio", "file") + ".jpg");*/
commands.add(image_path);
try {
ProcessBuilder builder = new ProcessBuilder();
builder.command(commands);
builder.start();
System.out.println("截取成功");
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public static JSONObject readVideoTimeFromCommand(String filePath){
JSONObject jsonObject = new JSONObject();
ProcessBuilder builder = new ProcessBuilder();
List<String> commands = new ArrayList<>();
commands.add(ffmpegPath);
commands.add("-i");
commands.add(filePath);
builder.command(commands);
builder.redirectErrorStream(true);
Process p = null;
try {
p = builder.start();
} catch (IOException e) {
e.printStackTrace();
}
// 获取执行输出信息
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8));
StringBuilder outInfo = new StringBuilder();
String line = "";
while (true) {
try {
if (!((line = br.readLine()) != null)) {
break;
}
} catch (IOException e) {
e.printStackTrace();
}
outInfo.append(line);
}
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
// 通过正则获取时长信息
String regexDuration = "Duration: (.*?), start: (.*?), bitrate: (\\d*) kb\\/s";
Pattern pattern = Pattern.compile(regexDuration);
Matcher matcher = pattern.matcher(outInfo.toString());
if (matcher.find()) {
return getTime(matcher.group(1));
}
return jsonObject;
}
/**
* 获取时间毫秒
* @param time 格式:"00:00:10.68"
* @return
*/
private static JSONObject getTime(String time) {
JSONObject jsonObject = new JSONObject();
System.out.println(time);
int index = time.lastIndexOf(".");
String distpath = time.substring(0, index);
jsonObject.put("time",distpath);
int min = 0;
String[] strs = time.split(":");
String ZERO="0";
if (strs[0].compareTo(ZERO) > 0) {
// 秒
min += Long.parseLong(strs[0]) * 60 * 60 * 1000;
}
if (strs[1].compareTo(ZERO) > 0) {
min += Long.parseLong(strs[1]) * 60 * 1000;
}
if (strs[2].compareTo(ZERO) > 0) {
min += Math.round(Double.parseDouble(strs[2]) * 1000);
}
System.out.println(min);
jsonObject.put("min",min);
return jsonObject;
}
}
/**
* 资源释放流.避免内存溢出导致进程阻塞
*/
class PrintStream extends Thread {
java.io.InputStream __is = null;
public PrintStream(java.io.InputStream is) {
__is = is;
}
@Override
public void run() {
try {
while (this != null) {
int _ch = __is.read();
if (_ch != -1){
System.out.print((char) _ch);
}else{
break;
}
}
__is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static String secondsToTime(int seconds){
int h=seconds/3600; //小时
int m=(seconds%3600)/60; //分钟
int s=(seconds%3600)%60; //秒
String hh = "";
String mm = "";
String ss = "";
if(h>0){
if(h<9){
hh = "0"+h;
}else{
hh = ""+h;
}
if(m<9){
mm = "0"+m;
}else{
mm = ""+m;
}
if(s<9){
ss = "0"+s;
}else{
ss = ""+s;
}
return hh+":"+mm+":"+ss;
}
if(m>0){
if(m<9){
mm = "0"+m;
}else{
mm = ""+m;
}
if(s<9){
ss = "0"+s;
}else{
ss = ""+s;
}
return "00:"+mm+":"+ss;
}
if(s<9){
ss = "0"+s;
}else{
ss = ""+s;
}
return "00:00:"+ss;
}
public static void main(String[] args) {
//获取时长和大小
ConvertVideo.readVideoTimeFromCommand("文件路径");
//截图 最后一个参数需要是 00:00:00格式的 截图最好截图前10秒的图,要不然截图会不成功
// ConvertVideo.processImg("源文件", "图片存放位置" + ".jpg", secondsToTime("视频文件大小"));//截图
//视频转换
// ConvertVideo.process("源文件路径", "转成的mp4文件路径", "中间avi临时文件路径");
}
}
注:需要用到两个文件
链接:https://pan.baidu.com/s/1qZb7CqcUxTZDe-nYwsnWsQ
提取码:abcd
链接:https://pan.baidu.com/s/1oPzD1ZNCgF3HCKfi8aTEzg
提取码:abcd
注意这种转换不管是获取时长,截图还是视频转换如果是多线程执行的话,一个线程执行完成另一个线程才会被执行,相当于多线程没有啥用,但是转换太费时间了,所以获取时长和截图我又找了其他的方法
上代码:
import org.apache.commons.io.FileUtils;
import org.springframework.web.multipart.MultipartFile;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.info.MultimediaInfo;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
public class VideoUtil {
/**
* 上传视频,获取视频时长,返回毫秒
* @param multipartFile
* @return
*/
public static long getDurationBackMillis(MultipartFile multipartFile){
if(multipartFile != null){
try{
// 根据上传的文件名字,构建初始化的文件对象(临时文件),这个文件是空的
File file = new File(multipartFile.getOriginalFilename());
// 通过工具类,将文件拷贝到空的文件对象中
FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), file);
// 将普通文件对象转换为媒体对象
MultimediaObject multimediaObject = new MultimediaObject(file);
// 获取媒体对象的信息对象
MultimediaInfo info = multimediaObject.getInfo();
// 从媒体信息对象中获取媒体的时长,单位是:ms
long duration = info.getDuration();
// 删除临时文件
file.delete();
return duration;
} catch(Exception e){
return 0L;
}
}
return 0L;
}
/**
* 上传视频,获取视频时长,返回时分秒字符串
* @param multipartFile
* @return
*/
public static String getDurationBackString(MultipartFile multipartFile){
// 获取视频时长,返回毫秒
long duration = getDurationBackMillis(multipartFile);
// 毫秒转时分秒的转换
// 日期格式化对象,给时分秒格式
SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
// 这里很重要,如果不设置时区的话,输出结果就会是几点钟,而不是毫秒值对应的时分秒数量了。
formatter.setTimeZone(TimeZone.getTimeZone("GMT+00:00"));
// 毫秒转化为字符串
return formatter.format(duration);
}
}
import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber;
import sun.misc.BASE64Encoder;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
public class DealVideo {
/*默认图片格式 jpg*/
public static String DEFAULT_IMG_FORMAT = "jpg";
/**
* 获取指定视频的帧并保存为图片JPG格式至指定文件
*
* @param videoFile 源视频文件路径
* @param targetFile 截取帧的图片存放路径
* @param frameNum 截取第几帧
* @throws Exception
*/
public static void fetchFrameToFile(String videoFile, String targetFile, int frameNum) {
//校验输入和输出
checkInAnOut(videoFile, targetFile);
try {
File frameFile = new File(targetFile);
FFmpegFrameGrabber ff = new FFmpegFrameGrabber(videoFile);
ff.start();
int length = ff.getLengthInFrames();
/*第几帧判断设置*/
if (frameNum < 0) {
frameNum = 0;
}
if (frameNum > length) {
frameNum = length - 5;
}
//指定第几帧
ff.setFrameNumber(frameNum);
int i = 0;
Frame f = null;
while (i < length) {
// 过滤前5帧,避免出现全黑的图片,依自己情况而定
f = ff.grabFrame();
if ((i >= 5) && (f.image != null)) {
break;
}
i++;
}
opencv_core.IplImage img = f.image;
int width = img.width();
int height = img.height();
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
bi.getGraphics().drawImage(f.image.getBufferedImage().getScaledInstance(width, height, Image.SCALE_SMOOTH),
0, 0, null);
ff.flush();
ff.stop();
ImageIO.write(bi, DEFAULT_IMG_FORMAT, frameFile);
} catch (Exception e) {
//throw new RuntimeException("转换视频图片异常");
e.printStackTrace();
}
}
/**
* 获取指定视频的帧并保存为图片自定义类型至指定文件
*
* @param videoFile 源视频文件路径
* @param targetFile 截取帧的图片存放文件路径
* @param outImgFormat 输出图片格式
* @param frameNum 截取第几帧
* @throws Exception
*/
/* public static void fetchFrameToFile(String videoFile, String targetFile, String outImgFormat, int frameNum) throws FrameGrabber.Exception, IOException {
//校验输入和输出
checkInAnOut(videoFile, targetFile);
try {
File frameFile = new File(targetFile);
FFmpegFrameGrabber ff = new FFmpegFrameGrabber(videoFile);
ff.start();
int length = ff.getLengthInFrames();
*//*第几帧判断设置*//*
if (frameNum < 0) {
frameNum = 0;
}
if (frameNum > length) {
frameNum = length - 5;
}
//指定第几帧
ff.setFrameNumber(frameNum);
int i = 0;
Frame f = null;
while (i < length) {
// 过滤前5帧,避免出现全黑的图片,依自己情况而定
f = ff.grabFrame();
if ((i >= 5) && (f.image != null)) {
break;
}
i++;
}
opencv_core.IplImage img = f.image;
int width = img.width();
int height = img.height();
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
bi.getGraphics().drawImage(f.image.getBufferedImage().getScaledInstance(width, height, Image.SCALE_SMOOTH),
0, 0, null);
ff.flush();
ff.stop();
// ImageUtils2 工具类旋转图片
BufferedImage ret = ImageUtils2.rotateClockwise0(bi);
ImageIO.write(ret, outImgFormat, frameFile);
} catch (Exception e) {
throw new RuntimeException("转换视频图片异常");
}
}*/
/**
* 获取指定视频的帧图片,并转换为base64字符串
*
* @param videoFile 源视频文件路径
* @param frameNum 截取第几帧
* @throws Exception
*/
public static String fetchFrameToBase64(String videoFile, int frameNum) throws FrameGrabber.Exception, IOException {
//校验输入
checkVideoFile(videoFile);
try (ByteArrayOutputStream output = new ByteArrayOutputStream();) {
FFmpegFrameGrabber ff = new FFmpegFrameGrabber(videoFile);
ff.start();
int length = ff.getLengthInFrames();
/*第几帧判断设置*/
if (frameNum < 0) {
frameNum = 0;
}
if (frameNum > length) {
frameNum = length - 5;
}
//指定第几帧
ff.setFrameNumber(frameNum);
int i = 0;
Frame f = null;
while (i < length) {
// 过滤前5帧,避免出现全黑的图片,依自己情况而定
f = ff.grabFrame();
if ((i >= 5) && (f.image != null)) {
break;
}
i++;
}
opencv_core.IplImage img = f.image;
int width = img.width();
int height = img.height();
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
bi.getGraphics().drawImage(f.image.getBufferedImage().getScaledInstance(width, height, Image.SCALE_SMOOTH),
0, 0, null);
ImageIO.write(bi, DEFAULT_IMG_FORMAT, output);
// 这里需要获取图片的base64数据串,所以将图片写到流里面
ff.flush();
ff.stop();
return new BASE64Encoder().encode(output.toByteArray());
} catch (Exception e) {
throw new RuntimeException("转换视频图片异常");
}
}
/**
* 校验输入输出
*
* @param videoFile
* @param targetFile
*/
public static void checkInAnOut(String videoFile, String targetFile) {
checkVideoFile(videoFile);
checkTargetFileDir(targetFile);
}
/**
* 验证文件目录是否存在,不存在就创建
*
* @param targetFile 文件路径
* @return
*/
public static void checkTargetFileDir(String targetFile) {
String dirPath = targetFile.substring(0, targetFile.lastIndexOf(File.separator) + 1);
File dir = new File(dirPath);
if (!dir.exists()) {
dir.mkdirs();
}
}
/**
* 检验文件是否存在
*
* @param videoFile
*/
public static void checkVideoFile(String videoFile) {
File file = new File(videoFile);
if (!file.exists()) {
throw new RuntimeException("文件不存在");
}
}
}
我上传的代码demo大概代码
//先上传源文件到系统
savePath = CommonUtils.upload(file, bizPath, uploadType,sy);
//获取源文件的时长和大小以及截图
//时长
String durationBackString = VideoUtil.getDurationBackString(file);
//大小
long durationBackMillis = VideoUtil.getDurationBackMillis(file);
//截图
DealVideo.fetchFrameToFile(inputPath,distpath + newnames + ".jpg",i1);
//异步转换
task.task1(inputPath, outputPath, outputPath1, uuid);
异步线程池
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class ThreadPoolConfig {
/**
* 每秒需要多少个线程处理?
* tasks/(1/taskcost)
*/
private int corePoolSize = 3;
/**
* 线程池维护线程的最大数量
* (max(tasks)- queueCapacity)/(1/taskcost)
*/
private int maxPoolSize = 100;
/**
* 缓存队列
* (coreSizePool/taskcost)*responsetime
*/
private int queueCapacity = 10;
/**
* 允许的空闲时间
* 默认为60
*/
private int keepAlive = 100;
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(corePoolSize);
// 设置最大线程数
executor.setMaxPoolSize(maxPoolSize);
// 设置队列容量
executor.setQueueCapacity(queueCapacity);
// 设置允许的空闲时间(秒)
//executor.setKeepAliveSeconds(keepAlive);
// 设置默认线程名称
executor.setThreadNamePrefix("thread-");
// 设置拒绝策略rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
return executor;
}
}
在启动类加上异步
文章来源:https://www.toymoban.com/news/detail-596516.html
异步任务代码文章来源地址https://www.toymoban.com/news/detail-596516.html
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.util.ConvertVideo;
import org.jeecg.common.util.FileUrlUtil;
import org.jeecg.common.util.MinioUtil;
import org.jeecg.modules.business.num.entity.YwVisit;
import org.jeecg.modules.business.num.service.IYwVisitService;
import org.jeecg.modules.business.video.entity.YwVideo;
import org.jeecg.modules.business.video.service.IYwVideoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import org.springframework.util.concurrent.ListenableFuture;
import java.util.Random;
@Component
@Slf4j
public class MyTask {
@Autowired
private IYwVideoService videoService;
@Autowired
private IYwVisitService ywVisitService;
@Async("taskExecutor")
public synchronized void task1(String inputPath,String outputPath,String outputPath1,String uuid) throws Exception {
System.out.println("正在执行线程" + Thread.currentThread().getName() + " 执行异步任务一"+uuid);
boolean process = ConvertVideo.process(inputPath, outputPath, outputPath1);
System.out.println("转换完成");
YwVideo video = videoService.getOne(new QueryWrapper<YwVideo>()
.eq("uuid", uuid)
);
if(null!=video){
if(!process){
video.setFlag("2");
//根据uuid 更新状态
//throw new Exception("转换错误");
}else{
video.setFlag("1");
}
//根据uuid更新状态
videoService.updateById(video);
}else{
YwVisit visit = new YwVisit();
visit.setUuid(uuid);
ywVisitService.save(visit);
}
FileUrlUtil.removeObject(inputPath,"minio");
FileUrlUtil.removeObject(outputPath1,"minio");
}
@Async("taskExecutor")
public synchronized void test(int num) throws Exception {
System.out.println("正在执行线程" + Thread.currentThread().getName() + " 执行异步任务"+num);
}
}
到了这里,关于java 视频统一转成mp4格式,并且异步多线程上传的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!